Este es el segundo post de la serie sobre node.js (socket.IO y mongoDB), si no lo hicieron lean el primero post #1.

Este capitulo es sobre el manejo de rutas, como ya sabrán cuando creamos nuestro servidor (en nuestro caso usando express) debemos declarar las rutas que manejaremos, y si bien esto a primera vista parece fácil hay algunas cosas a tener en cuenta.

Comencemos con algo simple, establecer la respuesta para la ruta “/”

1
2
3
4
app.get('/',function(req,res){
 res.write('handler : / \\n');
 res.end();
});

Con lo anterior establecemos como manejaremos un requerimiento de nuestro index (ej: http://javierviola.blogspot.com) y que para este post en particular responderemos simplemente con el texto handler : /.

Antes de seguir una aclaración, para demostrar el uso de rutas utilice simplemente los métodos res.write y res.end con el fin de imprimir la función que respondió el requerimiento y los parámetros pasados.

Habiendo dicho eso, sigamos…

Probamos la ruta /

1
2
3
4
5
lucaMac:02-Routes pepo$ curl localhost:3000
handler : /

lucaMac:02-Routes pepo$ curl localhost:3000/
handler : /

Vemos que responde a los dos requerimientos, ya que express se encarga de esa última barra (/) por nosotros.

Ahora imaginemos que nuestra aplicación maneja usuarios, por lo que definimos lo siguiente:

1
2
3
4
5
6
7
8
9
app.get('/users/:id?',function(req,res){
 res.write('handler: /users/:id? \\n');
 res.write('parametros: \\n');
 for(key in req.params){
  res.write('\\t'+key+' : '+req.params\[key\]);
 }
 res.write('\\n');
 res.end();
});

Esto nos permitiría responder tanto a un GET de /users como a /users/12 o /users/pedro, y esto es posible porque el primer parámetro de la funci&oacuet;n get express lo utiliza como una expresión regular, por lo cual esta definición puede servir para lista todos los usuarios, algun usuario o los usuarios que matcheen con el valor de :id.

Y esto es posible porque :id define un placeholder lo cual nos permite acceder al valor a través del objeto req.params.

Veamos las diferentes respuestas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
lucaMac:02-Routes pepo$ curl localhost:3000/users
 handler: /users/:id?
 parametros:
  id : undefined
 lucaMac:02-Routes pepo$ curl localhost:3000/users/pedro
 handler: /users/:id?
 parametros:
  id : pedro
 lucaMac:02-Routes pepo$ curl localhost:3000/users/25
 handler: /users/:id?
 parametros:
  id : 25

También podemos definir varios placeholders simplemente cambiando el primer argumento:

1
2
3
4
5
6
7
8
app.get('/users/:id/:action',function(req,res){
   res.write('handler: /users/:id \\n');
   res.write('parametros: \\n');
   for(key in req.params){
    res.write('\\t'+key+' : '+req.params\[key\]+'\\n');
   }
  res.end();
  });

Lo cual nos permite acceder a los parámetros id y action.

Y si bien esto es muy útil, podemos hacer cosas más complejas gracias a las regex como por ejemplo:

1
2
3
4
5
6
7
8
app.get('/users/:id(\[0-9\]+)/:action(edit|delete|create)',function(req,res){
  res.write('handler: /users/:id \\n');
  res.write('parametros: \\n');
  for(key in req.params){
   res.write('\\t'+key+' : '+req.params\[key\]+'\\n');
  }
  res.end();
 });

Y como pueden observar, aquí no sólo accedemos a id y action, sino que restringimos el primero a que sea numérico y damos tres opciones para el segundo, gracias a las regex :)

Veamos como funciona:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
curl localhost:3000/users/25/edit
 handler: /users/:id
 parametros:
  id : 25
  action : edit
 lucaMac:02-Routes pepo$ curl localhost:3000/users/25/delete
 handler: /users/:id
 parametros:
  id : 25
  action : delete
 lucaMac:02-Routes pepo$ curl localhost:3000/users/25/del
 Cannot GET /users/25/del
 lucaMac:02-Routes pepo$ curl localhost:3000/users/pedro/delete
 Cannot GET /users/pedro/delete

Como vemos funciona y si la regex no se cumple 404 no se puede encontrar….

1
2
3
4
5
lucaMac:02-Routes pepo$ curl localhost:3000/users/pedro/delete -I
 HTTP/1.1 404 Not Found
 X-Powered-By: Express
 Content-Type: text/plain
 Connection: keep-alive

* Ya veremos al final una posibilidad de manejar esto de una forma más pro

Bueno ya manejamos los GETs ahora queremos manejar los POSTs para ello definimos:

1
2
3
4
5
6
7
8
app.post('/users',function(req,res){
 res.write('handler post: /users/ \\n');
 res.write('parametros en body: \\n');
 for(key in req.body){
  res.write('\\t'+key+' : '+req.body\[key\]+'\\n');
 }
 res.end();
});

Y en este caso nos valemos del middleware bodyParser que nos permite acceder a los valores pasados a través del objeto req.body.

Una cosa interesante de express es que nos ofrece el middleware methodOverride que nos permite ser más semántico en nustras rutas, ya que no permite que un POST sea transformado en un PUT o y así ser manejado por app.put o app.delete respectivamente…

Para ello veamos la siguiente definición:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/\* post \*/
 app.post('/users',function(req,res){
  res.write('handler post: /users/ \\n');
  res.write('parametros en body: \\n');
  for(key in req.body){
   res.write('\\t'+key+' : '+req.body\[key\]+'\\n');
  }
  res.end();
 });

 /\* We can use put to be more semantics, like a RESTful api.
   For this be use the middleware methodOverride, this
   middleware check for the input \_method in the body
   (this input could be hidden for don't show to the
   end user), and if its present tranform the post in the
   \_method input value and don't pass this in req.body
  \*/
 app.put('/users',function(req,res){
  res.write('handler put: /users/ \\n');
  res.write('parametros en body: \\n');
  for(key in req.body){
   res.write('\\t'+key+' : '+req.body\[key\]+'\\n');
  }
  res.end();
 });

 /\* delete, as in put we can use methodOverride to be
    more semantic
 \*/
 app.delete('/users',function(req,res){
  res.write('handler delete: /users/ \\n');
  res.write('parametros en body: \\n');
  for(key in req.body){
   res.write('\\t'+key+' : '+req.body\[key\]+'\\n');
  }
  res.end();
 });

Como vemos definimos la misma ruta para los tres verbos http (POST,PUT,DELETE), y gracias al middleware methodOverride atender cada uno de ellos según el valor del input _method que se pase, y para explicarlo mejor un ejemplo:

Un post común:

1
2
3
4
5
lucaMac:02-Routes pepo$ curl -X POST -d "id=32&action=delete" localhost:3000/users/
 handler post: /users/
 parametros en body:
  id : 32
  action : delete

Un post, pero que será respondido como put:

1
2
3
4
5
lucaMac:02-Routes pepo$ curl -X POST -d "id=32&action=delete&\_method=put" localhost:3000/users/
 handler put: /users/
 parametros en body:
  id : 32
  action : delete

vean como se pasa el valor de _method para que el middleware sobreescriba el método

Y por último el mismo post, pero respondido como delete

1
2
3
4
5
lucaMac:02-Routes pepo$ curl -X POST -d "id=32&action=delete&\_method=delete" localhost:3000/users/
 handler delete: /users/
 parametros en body:
  id : 32
  action : delete

Y con esto queda explicado el middleware…

Una cosa importante a tener en cuenta es que las rutas se procesan secuencialmente y en la ubicación que fueron declaradas, es por eso que es muy importante hacer una declaración ordenada de nuestras rutas para que cada requerimiento sea atendido por la ruta que queramos y no por otra

Y haciendo uso de esto último, y utilizando un wildcard podemos manejar los errores 404 de una forma simple con esta ruta al final de todo:

1
2
3
4
5
app.all('\*',function(req,res){
  res.write('handler all: \*\\n');
  res.write('ERR, no regex match\\n');
  res.end();
 });

De esta forma atenderemos cualquier ruta (*) y en cualquier verbo http que no haya sido manejada anteriormente, con lo cual es muy útil para servir errores 404

Veamos como funciona:

1
2
3
4
5
6
lucaMac:02-Routes pepo$ curl -X POST -d "id=32&action=delete" localhost:3000/users/dasdsdas
 handler all: \*
 ERR, no regex match
 lucaMac:02-Routes pepo$ curl -X GET -d "id=32&action=delete" localhost:3000/usedasd
 handler all: \*
 ERR, no regex match

Espero que haya sido claro en como es el uso de rutas con express y cualquier cosa no duden en contactarme Les dejo el código utilizado Código en Github

Hasta la próxima (con la explicación de como funcionan los middlewares :) )

prettyPrint();