Vamos a profundizar un poco en esto de las rutas, que es una de las partes centrales de express(y yo diría de Node casi).
Las rutas es lo que hemos llamado anteriormente métodos (el get que configuramos para probar), y como ya comentamos y vimos en la documentación oficial de express, tenemos lo siguiente

app.method(urlPath,handler)

Siendo:

  • app: Ya vimos que es una instancia de express.Esta parte puede ser tambien router, ya veremos porque.
  • method:
    • GET: Se usa para pedir datos, y siempre que se le llame tiene que devolver lo mismo(siempre que la petición se haga de la misma forma)
    • POST: Para crear algo, un objeto de nuestra app
    • PUT: Se usa para actualizar datos u objetos existentes.
    • DELETE: Elimina datos u objetos existentes y al igual que el get siempre tiene que darnos el mismo resultado.
    • ALL: Con all, express aceptaría cualquiera de los anteriores, siempre y cuando el path coincida. Un posible uso sería para verificar credenciales si por ejemplo un token de sesión ha caducado y el usuario puede estar haciendo cualquier cosa, es decir, usando cualquiera de los otros métodos.
  • urlPath: Será el path que si coincide con la petición que ha recibido Node ejecutará su handler asociado
  • handler: Es el callback o función que se ejecuta si coincide el path.

Un detalle importante es que se comprueban las coincidencias de rutas en el orden que están escritas por lo que puede ser una buena opción si por ejemplo queremos comprobar si un usuario tiene permiso para acceder a cierta ruta o path.

Otro tipo de ruta o middleware es la de ficheros estáticos como pueden ser imágenes,css o ficheros javascript, ya comenté un poco en el post anterior como era ya que por defecto la carpeta public estaba configurada como tal, veamoslo un poco en detalle

app.use(express.static(path.join(__dirname, 'public')));

Con esto lo que le estamos indicando a express y a node con este middleware es que todo lo que este en la carpeta public será servido como estático, ojo teniendo que usar la estructura de carpetas existentes, me refiero a que si debajo de public tenemos una carpeta images el path deberia ser urlRaiz/images/imagen.jpg. Vamos a comprobarlo con el fichero css que nos crea por defecto express generator, que en este caso esta dentro de la carpeta stylesheets y tiene el nombre style.css, entonces como hemos comentado la ruta sería (con nuestro proyecto iniciado claro está)

http://localhost:3000/stylesheets/style.css

Lo que nos devolvería el fichero css en el navegador
Css static example

También podemos indicarle a express que nos sirva los estáticos a través de un path o ruta virtual que nosotros le indiquemos, en lugar desde la ruta raiz. Si ponemos esta línea en el fichero app.js de nuestro proyecto (cambiandola por la que nos servía estáticos antes)

app.use('/ninja_static',express.static(path.join(__dirname, 'public')));

entonces nuestra url cambiaría a

http://localhost:3000/ninja_static/stylesheets/style.css

Esto es muy útil si por ejemplo tenemos una estructura de carpetas de estáticos muy larga y queremos realemente acortar las url, podríamos por ejemplo cambiarlo por

app.use('/ninjacss',express.static(path.join(__dirname, 'public/stylesheets')));

Lo que nos convierte la url a

http://localhost:3000/ninjacss/style.css

Como véis podemos gestionar nuestras rutas como queramos para que sea más cómodo trabajar con ellas.

Rutas/Middlewares con parámetros

Hasta ahora habíamos visto siempre como trabajar con rutas digamos estáticas pero claro ninguna API es nunca tan simple, generalmente en la URL pasamos información para recibir un elemento específico, imaginemos por ejemplo que estamos creando una API que nos devuelva información sobre todos los superhéroes/villanos de los comics del mundo, ¿cuántos podrían ser? Miles como mínimo, lo que supondría tener que hacer una API con miles de middlewares ¿no?...por suerte no es necesario ya que sería algo inviable, ningún servicio aguantaría eso, lo que hacemos es que nuestro middleware reciba parámetros por ejemplo en la url, con este estilo

http://heroapi.com/hero/57

o este por ejemplo

http://heroapi.com/hero?name=batman

Tenemos varias formas de hacerlo, para finalmente buscar la información usando ese parámetro, todo en el mismo middleware.
En general tenemos cuatro formas de hacerlo:

  • En la propia ruta: /hero/57
  • Usando query string: /hero?name=batman, se conoce query string a lo que está destrás de ? y son elementos de variable=valor
  • Puede ir en el cuerpo(body) de la petición, esto generalmente se usa para peticiones de tipo POST o PUT, ya que al almacenar/actualizar solemos enviar muchos datos y pasarlo por query string puede ser casi imposible.
  • Otra opción puede ser la cabecera, aunque esta opción exista simplemente porque podemos acceder a la cabecera desde el middleware, pero no se usa para datos triviales (como buscar un héroe), si no para datos como token de autenticación, formatos y cosas así.

En la ruta

Empecemos con un ejemplo con usando la ruta, nos vamos a nuestro proyecto de ejemplo ninja_project y vamos a modificar la ruta del index.js poniéndole un parámetro para cambiar el título. Quedaría así:

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/:title', function(req, res, next) {
  var newTitle = req.params.title
  res.render('index', { title: newTitle });
});

module.exports = router;

Como véis a la ruta le hemos puesto :title y luego simplemente hemos sacado esa información de los parámetros (params) de la petición (request).
Si ejecutamos con npm start y probamos por ejemplo con

http://localhost:3000/Ninja

Tendríamos este resultado
Middleware route params example
Usando las rutas como paso de parámetros tenemos varias opciones, para por ejemplo si queremos que el parámetro sea opcional(si probáis sin poner /Ninja os daria un error el ejemplo anterior) ponemos la ruta de esta manera

router.get('/:title?', function(req, res, next) {

También es posible usar expresiones regulares para que por ejemplo solo admita números

router.get('/:id([0-9]+)', function(req, res, next) {

Y es posible poner varios

router.get('/:type(villain|hero)/data/:name)', function(req, res, next) {

Esta aceptaría en type solo villain o hero y en name cualquier cosa.
Las rutas son bastante dinámicas para poder gestionar middlewares de manera más cómoda y segura para nosotros(evitando datos de algún tipo que no queremos recibir).

Usando query string

Ahora veamos un ejemplo con la segunda forma, que veréis que es muy parecido.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  console.log(req.query);
  var newTitle = req.query.title;
  res.render('index', { title: newTitle });
});

module.exports = router;

Hemos vuelto a dejar la ruta o path como estaba y en lugar de obtener el dato de title de los parámetros de la url, lo hacemos con la query y ¿como usamos esto? He puesto también un console.log para que veáis como recibimos los elementos de tipo query string. Simplemente poner

http://localhost:3000/?title=Ninja

Al probar obtendríamos el mismo resultado, y si miráis el log de la consola veréis como recibe los parámetros de la query, como un objeto clave= valor
Middleware query string example
Log query string example

Métodos de respuesta

Hasta ahora hemos estado centrados todo el rato en ala captura de la petición, es decir, en la parte request pero ¿que pasa con la respuesta? Vamos a ver algunos de los métodos de respuesta más usados, pero lo mejor es que miréis la documentación que vienen todos y muy bien explicados:

  • res.send: Lo hemos usado ya en algún ejemplo, responde como puede a lo que le pongas jejeje, funciona bien con buffer, strings, objetos, arrays(estos dos últimos los devolveria como un JSON)...
res.send('Hola Ninjas')
  • res.render: También lo hemos usado, nos devuelve una página renderizada usando algún template y si acaso usando los parámetros que le pasemos
res.render('index',{title: 'Super Título Ninja'});
  • res.json: Respuesta específica como JSON
res.json({ name: 'Batman'});
  • res.download: Devuelve directamente un fichero, este sería el caso típico en el que nos aparece la ventana de guardar en el navegador
res.download('/rutaYNombreFichero.doc','nombreOpcionalDelFichero');
  • res.redirect: Redireccionando al usuario a otro sitio con un código de estado 302 por defecto(esto se puede cambiar, más abajo lo veremos). En este caso tenemos varias opciones para la redirección.
    • Ruta relativa al root del usuario:
    res.redirect('/root/path/algo');
    
    • Url absoluta:
    res.redirect('http://urlnueva');
    
    • Pasandole también el código de estado:
    res.redirect(301,'http://urlnueva');
    
    • Ruta según el path actual:
    res.redirect('../ruta/algo')
    
    • También podemos hacer que vuelva a la página anterior:
    res.redirect('back')
    

Si miráis la documentación hay muchos más y todos estos pueden admitir más parámetros, con más opciones de configuración. Comentar que casi todas las opciones de respuesta soportan cambiar el código de estado, solo tenemos que encadenar métodos

res.status(500).send('Error');
res.status(200).render('index');

Muy cómodo si queremos devolver algún código específico que no sea el de por defecto de cada respuesta.
Poco a poco veremos como van saliendo distintos tipos de respuesta en los ejemplos que vayamos haciendo.

Creando nuestra propia ruta

Hasta ahora hemos estado trabajando con lo que ya nos había configurado express, pero ¿y si queremos crear nuestra propia ruta?¿Cuáles serían los pasos? Bien, pues vamos a verlos.
Lo primero de todo es crearnos un fichero nuevo en la carpeta routes(para mantener el orden), por ejemplo lo voy a llamar ninjas.js y de momento le ponemos esto

'use strict'

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/heros', function(req, res, next) {
  res.json({ninjas: "Somos un montón de ninjas"})
});

module.exports = router;

Si os fijáis lo que hacemos es instanciar un Router, que es una de las formas de crear rutas para pasarselas a express. A este router le hemos indicado el método a usar, su path y su respuesta y por último lo hemos exportado como si de un módulo se tratara para así poder usarlo en cualquier parte de nuestra aplicación.
Lo siguientes es irnos al app.js que, como ya comentamos en su momento, entre otras cosas tiene la instanciación de las rutas, y preparamos la nuestra, para ello lo primero es requerirla, y lo hacemos debajo de las otras

var index = require('./routes/index');
var users = require('./routes/users');
var ninjas = require('./routes/ninjas');

Ya la tenemos disponible en nuestro app.js pero realmente todavia no le hemos indicado donde tiene que escuchar y que tiene que hacer, para hacerlo buscamos donde ha creado las otras y hacemos lo propio

app.use('/', index);
app.use('/users', users);
app.use('/ninjas',ninjas);

Y listo con esto ya tendríamos nuestra propia ruta, como véis es bastante sencillo. Ahora vamos a probarla peeeeero ojo al dato, tenemos que tener en cuenta:

  • La ruta o path que hemos puesto en el app.use
  • La ruta o path que hemos puesto en ninjas.js

Esto lo remarco porque realmente esto lo que esta haciendo es escuchar lo primero en el path /ninjas y según ese path lo que hace es indicarle al ninjas.js que es su turno (en plan enga majete a currar), pero claro este router tiene configurado también su propio path, es decir que a partir del path por el que ha sido convocado en el app.use tiene que cumplir el segundo que en este caso seria heros, por lo que la ruta quedariía así

http://localhost:3000/ninjas/heros

Y esto nos devolvería un json
Own router example
Una vez que entiendes como funciona, ves que realmente es bastante sencillo configurar y trabajar con las rutas de express.

Durante todo el post, ha estado apareciendo la palabra middleware pero , ¿que es exáctamente un middleware? Un middleware es un handler o manejador que responde a un tipo de petición o a todas. Básicamente es lo que hemos estado haciendo todo el rato. Veamos algunos detalles de su implementación.
Si os habéis fijado en nuestros middlewares que nos ha creado express, en los parámetros que recibe la función handler, tenemos esto

function(req, res, next) {

Hemos visto que es req y res pero ¿que es next?, básicamente next es un método para indicar que continue hasta el siguiente middleware si no queremos terminar la ejecución. Supongamos por ejemplo que queremos comprobar el token de acceso de un usuario o sin ir más lejos si un usuario existe antes de tener acceso a ningún otro middleware. En este caso podríamos crear un middleware que en todas las peticiones compruebe cualquiera de los dos casos y a continuación una vez comprobado simplemente le diríamos que continuará al siguiente middleware para darle acceso a lo que realmente ha solicitado, es decir, acceso al middleware que nos ha mandado como request.
Ahora vamos a ver un ejemplo muy sencillito para que se entienda mejor, pero antes comentar que si un middleware no responde de ninguna forma y no indica explicitamente que vaya al siguiente middleware no saldrá del handler en cuestión y nos dará timeout la petición. Vamos con el ejemplo, primero comprobemos que nos da timeout, para elloen nuestro app.js antes de indicar los middlewares ponemos los siguiente

app.use('/', function(req,res,next){
  console.log('MIddleware sin respuesta');
});
//Encima de este
app.use('/', index);

Y comprobamos lo que pasa al acceder
Middleware timeout
El log muestra nuestro nuevo middleware pero la ventana se queda a la espera de recibir algo
Timeout example
Ahora pongamos el next a ver que pasa

app.use('/', function(req,res,next){
  console.log('MIddleware sin respuesta');
  next();
});

Como véis en la consola ha pasado por el middleware(2 veces) y ha continuado hasta el siguiente middleware que coincide que es el index
Screen-Shot-2017-09-22-at-09.14.56
Y como no hemos pasado en la queryString(que es como lo dejamos antes) solo nos aparece el welcome
Screen-Shot-2017-09-22-at-09.17.10
Pero por lo menos vemos que ha continuado sin problemas.
Importante no podemos responder a un middleware y pasar al siguiente, lo que sea que queramos hacer tiene que ser al revés, es decir, no podemos responder 2 veces a la misma petición ya que el cliente no espera más de una respuesta, si en algún momento recibís en consola un log que ponga algo similar a header ya envíado o similar es porque estáis intentando responder dos veces.
Los middlewares podemos separarlos en varios tipos (como concepto de uso):

  • Tenemos los middlewares a nivel de aplicación, es decir, los que ejecutamos en toda actividad de la app, y son los que normalmente deberían estar en el app.js. Acabamos de usar uno
app.use('/', index);
  • Middleware a nivel de router: los que ponemos en la carpeta routes y responden a un tipo de petición o path específicos
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {}
  • Middleware de error: Estos los ponemos los últimos y como su nombre indica responderan cuando tengamos un error. Si miramos nuestro app.js vemos que tenemos uno al final. Se diferencian en que reciben un parámetro más err.
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

Normalmente son llamados por nosotros pasándoselo al next, si os fijáis en el app.js, justo encima del middleware de error tenemos uno que captura todas las peticiones que no han coincidido y le pasa un mensaje de tipo 404 al middleware de error con next(err)(básicamente en cuanto a next le pasemos algo irá al handler de error)

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});
  • Tenemos más middlewares oficiales, que básicamente son los mantenidos en el módulo connect que usa express. Podéis encontrar informacíon aquí.
  • Por último tenemos otros que llamaríamos middlewares de terceros, que son los creados y mantenidos por la comunidad, muchos de ellos aparecen en esta página de express

Creo que más o menos he comentado casi todo lo importante referente a los middlewares (ha sido un post más largo de lo habitual jejejejje), espero que a partir de ahora podáis hacer los vuestros sin problemas, en el siguiente post veremos un poco los templates y alguna cosa más. Sin mucho más nos veeemossss un abrazoooorrr