Está en la página 1de 172

Descubriendo Node.

js y Express
Aprende a desarrollar en Node.js y descubre cómo
aprovechar Express

Antonio Laguna
Este libro está a la venta en http://leanpub.com/descubriendo-nodejs-express

Esta versión se publicó en 2013-07-02

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

©2013 Antonio Laguna


¡Twitea sobre el libro!
Por favor ayuda a Antonio Laguna hablando sobre el libro en Twitter!
El tweet sugerido para este libro es:
¡Acabo de hacerme con Descubriendo Node.js y Express! #descubriendoNodejs
El hashtag sugerido para este libro es #descubriendoNodejs.
Descubre lo que otra gente está diciendo sobre el libro haciendo click en este enlace para buscar el
hashtag en Twitter:
https://twitter.com/search/#descubriendoNodejs
También por Antonio Laguna
Laravel: Code Happy (ES)
Índice general

Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Dedicado a… . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Errare humanum est . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Libro en desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii

Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Evolución de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Algunas presunciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Audiencia del libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi

Introducción a Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Node.js basado en eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

La asincronía por naturaleza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Instalando Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Instalando en Windows y Mac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Instalando en Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
¿Funciona mejor Node.js en algún sistema? . . . . . . . . . . . . . . . . . . . . . . . . . . 7
¿Qué acabamos de instalar? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Accediendo a la consola . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

¡Hola mundo! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Hola mundo… ¡en un servidor! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

La consola de Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
console.log y console.info . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
console.error y console.warn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
console.time y console.timeEnd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Accediendo a las variables del entorno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

Pasando parámetros a Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21


ÍNDICE GENERAL

NPM - Node Packaged Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23


Búsqueda de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Obtener información de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Lista de paquetes instalados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Instalación de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Desinstalación de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Paquetes útiles y habituales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Dudas frecuentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

Cómo mantener Node.js actualizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29


Sobre las versiones de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

Nuestra primera aplicación de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

Adentrándonos en Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Gestión de dependencias con package.json . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Versionado semántico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Estructura del archivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

Exportando en Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Exportando con el objeto exports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Exportando con module.exports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Algunas aclaraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Pasando parámetros a require . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

Organizando el código de nuestra aplicación . . . . . . . . . . . . . . . . . . . . . . . . . 49


El archivo de configuración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

Emisión de eventos con EventEmitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53


Patrón del observador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Emitiendo eventos con Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Pasando parámetros a los eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Dejando de escuchar eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Refactorizando el ¡Hola mundo! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Creando clases que emiten eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Un ejemplo real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

Los Streams en Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60


¿Que es un Stream? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
La función pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Lectura - Readable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
ÍNDICE GENERAL

Escritura - writable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Lectura y Escritura - Duplex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Transformación - Transform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Pasarela - PassThrough . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

El sistema de archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Leyendo ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Escribiendo en ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Los Streams y los ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

Introducción a Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Otros frameworks de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Meteor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Derby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
flatiron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
TowerJS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
¿Por qué Express? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

Instalaci�n de Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

Creando la estructura básica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

Welcome to Express - Bienvenido a Express . . . . . . . . . . . . . . . . . . . . . . . . . . 80

Configuración de la aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Guardando y obteniendo valores en la aplicación . . . . . . . . . . . . . . . . . . . . . . . 83
Configurando la aplicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

Rutas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Cadena de búsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

Middlewares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
app.use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
En línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Mapeado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
En resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Middlewares ofrecidos por Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

La petición - request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102


req.body . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
req.param(parametro) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
ÍNDICE GENERAL

req.is(tipo) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
req.ip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
req.xhr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

La respuesta - response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104


res.status . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
res.redirect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
res.send . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
res.jsonp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
res.sendfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
res.download . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
res.render . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

Plantillas con Jade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108


Sintaxis básica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Anidando elementos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Variables en Jade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Bloques de código auxiliares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

Páginas de error en Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

Gestión de Login con Passport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

Gestionando la subida de ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129


Subiendo varios ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

Apéndice A: Ejecuta tu aplicación Node.js “siempre” . . . . . . . . . . . . . . . 138


Ejecutando Node.js en segundo plano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

Usando forever . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140


Operaciones con los procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Consejo extra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

Apéndice B: Creando un chat con Socket.io . . . . . . . . . . . . . . . . . . . . . 144


¿Qué es Socket.io? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
La aplicación que vamos a crear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Instalando las dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
El lado servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
El lado del cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Introducción
Dedicado a…
A Laura, mi mujer. Ella me ha apoyado e impulsado a hacer esto posible. Sin ella, estoy seguro de
que este libro no habría tenido lugar. Además, ha tenido a bien el crear la (para mi) maravillosa
portada que da presencia al libro.
A mi padre. Él no solo me ha animado, si no que puso la semilla de la programación en mi y ese
gusanillo de pegarte horas delante del monitor, apasionado por la programación.
Por supuesto a Ryan Lienhart Dahl, creador de Node.js que ha hecho que toda esta revolución sea
posible.
A David López, por su inestimable ayuda ofreciendo un valiosísimo feedback con cada capítulo/sec-
ción/coma/espacio/trozo de código/etc que he ido publicando. ¡Gracias!
A la comunidad, Node.js no sería lo que es de no ser por esa gran cantidad de paquetes y apoyo que
recibe por parte de la comunidad.

Errare humanum est


O como dicen en mi pueblo, el que tiene boca se equivoca.
Me considero una persona bastante cuidadosa con la escritura y bastante obsesionado con las faltas
de ortografía y gramática. Eso no quita, que se me pueda haber escapado algo y seguramente, así
será. Si te encuentras con cualquier error en el libro, tanto de escritura como de código, te agradecería
que me avisaras enviándome un correo a a.laguna@funcion13.com¹ incluyendo el capítulo o frase
relevante.

Feedback
¿Tienes alguna crítica constructiva? ¿Quieres hacerme algún comentario? ¿Crees que alguna sección
podría ser mejorada? ¿Me quieres enviar un jamón? Para todo eso, y mucho más, envíame un correo
a a.laguna@funcion13.com².
¹mailto:a.laguna@funcion13.com
²mailto:a.laguna@funcion13.com
Introducción ii

Libro en desarrollo
¿Pero esto qué es? ¿¡He comprado un libro que no está terminado?! Me temo que sí. ¿Por qué he
hecho esto? Pues porque me gustaría nutrirme del feedback que vayáis pudiendo ofrecerme sobre el
libro conforme se va escribiendo y así poder ofrecer un libro que sea lo más útil posible al lector.
Planeo escribir el libro en tres tandas. Tanto en la primera como en la segunda, estará con un precio
reducido para incentivar su adquisición. Ten en cuenta que una vez te hagas con el libro, podrás
hacerte con todas las actualizaciones que este tenga, incluso me encargaré de que te llegue un correo
avisándote del hito.
Si te has aventurado a hacerte con el libro durante su gestación, gracias. De verdad. Ponte en
contacto conmigo³ y hazme saber qué te parece. ¡Como agradecimiento te colocaré en los créditos!
Actualmente esto es lo que está en desarrollo:

• Apéndice A - Uso de Socket.io y aplicación básica


• Apéndice B - Uso de conexión contra BD… ¿Mongo? ¿MySQL? ¿Ambos?

³mailto:a.laguna@funcion13.com
Introducción
Esto que lees, son las primeras palabras de mi primer libro, ¡un gran hito! Le he dado bastantes
vueltas a esto de la introducción así que espero que no quede tan mal después de todo.
Quizá te estés preguntando, ¿y quién es este para enseñarme Node.js?
Quizá no tenga experiencia con los libros, pero llevo bastante tiempo en la red intentando esparcir
mi conocimiento y, hasta ahora, ¡no me ha ido mal! Quizá hayas leído alguno de los tutoriales de
programación que escribí en Función 13⁴, mi web. O quizá, te hayas cruzado con alguna de mis guías
de World of Warcraft en una página de cuyo nombre no quiero acordarme. En cualquier caso… me
apasiona enseñar, y me apasiona el desarrollo, principalmente el desarrollo web. Es por ello que
te advierto que no encontrarás largas explicaciones ni frases rebuscadas, suelo ser bastante directo
aunque por ello aparente que no hay tanto contenido.
Voy a hablar un poco de mi, puedes saltártelo si no te interesa… lo entendería…
¿Aun sigues aquí? Entonces es que quieres leer algo sobre mi. Intentaré mantenerlo breve. Soy
Antonio Laguna y soy programador web. Original de un pequeño pueblo del Aljarafe sevillano,
en España. Desde pequeño tuve la suerte de trastear con los ordenadores. Con mi padre como
programador desde que la informática se inició, he tenido siempre a mano algún ordenador.
Recientemente me he mudado a Londres que, como espero que sepas, es la capital de Reino Unido
para trabajar en Gamesys. Mi empresa se dedica a realizar páginas de juegos de casino: ruleta, cartas,
tragaperras, bingo, etc. Tengo la suerte de trabajar con un gran equipo de profesionales de los que
estoy aprendiendo día a día. Mi trabajo no es diseñar las webs, es hacer que éstas cobren vida, gracias
a JavaScript principalmente.
Fue hace unos años ya que empecé a picarme con Node.js. Tenía una necesidad que cubrir en mi
anterior empresa, Accenture y todo lo que leía apuntaba a Node.js. Viniendo de PHP y con algunos
conocimientos básicos sobre JavaScript, me amarré a esa frase del “¿quién dijo miedo?” y aquí
estamos. En mi tiempo libre me encanta leer sobre desarrollo y escribir en mi blog. Pero dejemos de
hablar de mi, he venido a hablar de mi libro… digo de Node.js.
Node.js es una gran revolución. ¿JavaScript en el lado del servidor? ¿Quién iba a pensarlo? JavaScript
lleva mucho tiempo entre nosotros y ha evolucionado muchísimo como lenguaje y, por suerte, sigue
haciéndolo.

Vale… pero, ¿qué es Node.js?

Node.js es un sistema del lado del servidor, para escribir código que nos permite crear aplicaciones
web e incluso servidores web que responden a peticiones. Está creado sobre el motor JavaScript que
lleva Chrome (V8), lo cual lo hace realmente rápido.
⁴http://www.funcion13.com
Introducción iv

Según la página de Node.js, esta es la definición:

Node.js es una plataforma creada sobre el motor JavaScript de Chrome para crear
aplicaciones de red rápidas y escalables. Node.js utiliza un modelo basado en eventos
y entrada/salida que no bloquea, lo cual lo hace ligero y eficiente, perfecto para
aplicaciones que usen muchos datos en tiempo real que puedan ejecutarse de forma
distribuida en varios dispositivos.

Prometedor… ¿no? Además, puede que te interese saber que compañías como Microsoft, Linkedin o
Yahoo ya están usando Node.js para el software.

¿Un servidor?

Sí, has leído bien. Node.js es capaz de crear un servidor web y no tendrás la necesidad de usar algo
como Apache⁵, lighthttpd⁶ o Nginx⁷.

Pufff… ¡qué complicado!

¡No! ¡No lo es! Y eso es lo que pretendo demostrarte en este libro. Todas las personas que conozco que
han comenzado con Node.js, han acabado disfrutando mucho del entorno porque tienes muchísima
libertad a la hora de escribir código y sientes que todo está en tus manos.
Para que te hagas una idea, crear un servidor ocupa tan solo seis líneas de código… ¡como mucho!

1 var http = require('http');


2
3 var server = http.createServer(function (request, response) {
4 response.writeHead(200, {"Content-Type": "text/plain"});
5 response.end("¡Hola Mundo!\n");
6 }).listen(8080);
7
8 console.log('Servidor escuchando por el puerto 8080');

Tampoco quiero agobiarte ahora al principio, pero creo que es un código bastante sencillo de
entender.
Una de las mejores cosas que tiene Node.js son las librerías que puedes instalar y usar en tus propios
desarrollos. He aquí un breve listado:
⁵http://httpd.apache.org/
⁶http://www.lighttpd.net/
⁷http://nginx.org/en/
Introducción v

• Forever - Te permite ejecutar una aplicación Node.js para siempre, en caso de que algún error
acabe con ella, forever se encargará de levantarla nuevamente.
• Express - La veremos en este libro pero nos permite crear un servidor web y hacer virguerías
con él como responder a distintos verbos HTTP, ejecutar código antes y/o después de cada
ruta, etc.
• Socket.io - Comunicación en tiempo real con el navegador a través de WebSockets si están
disponible, o algunas soluciones menos rápidas en caso de ser otro navegador (Internet
Explorer).
• Jade - Un sistema de plantillas usado por Express que nos evita tener que escribir mucho
código para crear HTML.

Evolución de Node.js
Node.js ha ido cambiando muchísimo desde que apareció “al público general”. Poco a poco ha ido
revolucionando la tecnología y más y más empresas se animan a usar Node. Lo bueno, es que gracias
a su versatilidad, puede que estés usando ya algunas herramientas que usen Node… ¡sin saberlo!
Una de las cosas malas que tiene Node.js es que está en constante cambio. Aunque ahora están un
poco más calmados, al principio particularmente los cambios eran contínuos y la estabilidad algo
dudosa. ¡Pero eso ha cambiado! Dado el gran apoyo de muchas empresas y el interés general que
existe por Node, cada vez es más estable y robusto y los cambios no suelen ser tan drásticos.
Suelen sacar versiones bastante a menudo con la intención de solucionar errores y, especialmente,
mejorar el rendimiento. No obstante, a veces, el cambio de versión es bastante significativo e implica
que ciertas funciones dejen de funcionar como lo hacían antes. Por eso es muy importante revisar
la wiki⁸ o el blog⁹ antes de actualizar.
Habitualmente, Node.js mantiene dos versiones. Una inestable, que está en continuo desarrollo
y no está recomendada para el uso en producción, y otra estable que es la que normalmente
descargaremos para nuestras aplicaciones. Aunque ahora mismo no tienes que preocuparte de nada
de esto ya que, por regla general, te bastará con la versión estable.

Algunas presunciones
En este libro utilizaremos ejemplos basados en sistemas Unix. Esto no implica que no vayan a
funcionar en un entorno Windows, si no que verás comandos como ls que no funcionarán en
Windows. Aunque no es algo que ocurrirá muy a menudo e intento minimizarlo lo máximo posible,
tenlo en cuenta si estás siguiendo el libro en Windows.
⁸https://github.com/joyent/node/wiki
⁹http://blog.nodejs.org/
Introducción vi

Audiencia del libro


Este libro está pensado para lectores que tengan algo de experiencia con algún lenguaje de
orientación a objetos: Ruby, Python, PHP o Java incluso; algo de experiencia con JavaScript y, por
supuesto… ¡nuevos con Node.js!
En este libro no vamos a explicar los tipos de variables, cómo funciona un bucle for o qué valores
se evalúan como false en JavaScript. Lamento decirte que, sin estos conocimientos, quizá te sientas
algo perdido.
No obstante, no pretendo que seas un ninja de JavaScript, ni un gurú, ni tururú. Es más, en las cosas
especialmente oscuras de JavaScript me detendré si lo veo necesario para ayudarte en el camino del
aprendizaje de Node.js y, porqué no, de JavaScript.
Puede que te estés preguntando, ¿todo lo que sé de JavaScript lo puedo usar en Node.js? Sí y no.
Lo mismo ocurre a la inversa. Node.js vive en el servidor por lo que no tendrás un objeto window
ni dispondrás del método alert(), a su vez, el navegador no tiene acceso a funciones de ficheros
o a los Streams por ejemplo (sean lo que sean). No obstante, la sintaxis y las palabras claves son
exactamente las mismas con la salvedad de que Node.js usa versiones más modernas de JavaScript
que solo están disponibles en los últimos navegadores.
Observa que el título del libro indica claramente Descubre. Este no es un libro para convertirte en
Maestro, ni en Ninja ni nada por el estilo. Es para ayudarte en el camino del conocimiento. Por la
red esparcida hay mucho conocimiento sobre Node.js pero no todo está actualizado y casi todo está
desperdigado en diferentes sitios. Mi intención es que comiences conmigo el aprendizaje. Te daré las
herramientas y los conocimientos para comenzar a crear tus propias aplicaciones (sean o no web).
Bueno… ¿Estás preparado para sumergirte en el mundo de Node.js? ¡Pues adelante, sigue leyendo!
¡Nos vemos en el primer capítulo!
Introducción a Node.js

En la introducción de el libro ya hemos hablado un poco de Node.js… ¡y hasta hemos puesto un


sencillo ejemplo! pero no quiero dejar de profundizar un poco más.
Si eres el tipo de lector al que va dirigido este libro, probablemente no tengas mucha experiencia
con JavaScript. Tocas aquí un poco, un poco de jQuery por allí, este módulo de pop-ups por acá y
un slideshow por allá. ¿Qué hay en común en todo esto? Que estamos en el cliente, el navegador.
Si eres de esos que usan jQuery muy a menudo para hacer animaciones en la web, o seleccionar un
elemento de la página, no es con eso con lo que te vas a encontrar aquí.
Node.js es JavaScript del lado del servidor. Realmente es algo más que JavaScript, es un 70%
JavaScript y el resto es principalmente C / C++. Es por ello que tienes que intentar cambiar el chip.
Quizá te sea más sencillo compararlo con un entorno PHP, Ruby o Python (todos amigos por igual…).
Para trabajar con Node.js, nos valdremos de la línea de comandos en muchas ocasiones ya que, en
el fondo, no es más que una interfaz de línea de comandos. ¡Te vas a sentir todo un hacker! Sé que
muchas personas le tienen miedo a la línea de comandos, pero como dice Bane…

1 Yo nací en la línea de comandos, moldeado por ella. No vi Windows hasta que\


2 ya era un hombre...

Créeme cuando te digo que no tienes por qué preocuparte, ya que no vamos a usar ningún comando
raro en ningún caso. ¿Confiarás en mi?
El pilar fundamental de Node.js, es que está basado en eventos y su naturaleza de no bloqueo. No,
no estamos hablando del tipo de bloqueo del parchís. Si no de operaciones que bloquean (o no) el
servidor. Vamos a meternos un poco más en faena.
Node.js basado en eventos
La aplicación habitual de Node.js, se queda residiendo en memoria y espera algo por parte del sistema
para ejecutar una función. Esto puede ser alguien entrando en una página web, un cambio en un
fichero de un directorio o un mensaje que llegue por un socket. Piensa en ello como una gasolinera
24 horas. Está ahí abierta y puede pasar horas sin que nadie vaya, pero eventualmente alguien pasará
a repostar y estará lista para surtir gasolina.
¿Y qué hace durante el resto del tiempo? Nada. De hecho es bueno que no haga nada ya que es
necesario para que se active el recolector de basura de JavaScript.

¿Basura? ¿Qué pasa si no se recolecta?

Los recolectores de basura existen en la gran mayoría de los lenguajes. Son como eso que imaginas
solo que no son personas. Es un proceso que se encarga de revisar el código que está en ejecución y
decir…

1 A ver, ¿esta variable sigue existiendo? No, ¿no? Pues la quitamos de la mem\
2 oria
3 ¿Y esta función ha terminado ya? Quitemos todas sus variables de la memoria\
4 .

Estoy seguro, lector, de que ya sabes lo que pasa si no recolectamos la basura. Que, eventualmente,
nos quedaríamos sin memoria y eso sería una catástrofe. Todo empezaría a ir lento. ¡No solo la
aplicación! ¡Si no el sistema!

¿Y puedo yo llamar al recolector de basura?

No, no puedes ni debes. Si seguimos buenas prácticas de programación no tendríamos nunca que
preocuparnos por este proceso invisible.

Vale Antonio, ¿pero todo esto no lo puedo hacer con Python o PHP?

Seguro que estás pensando en eso. Mucha gente lo piensa, especialmente los Javasaurios. Y tienes
razón. Node.js no es la panacea y no hará todo el trabajo por ti. Es una herramienta que, en manos
inexpertas puede parecer poca cosa. En este libro vamos a intentar aprovechar todo ese potencial
que está dando tan buenos resultados por ahí.
Node.js es especialmente bueno cuando queremos hacer cosas a la vez. ¿No te has visto en la tesitura
nunca de decir, ojalá pudiera hacer cosas en paralelo? Pues en Node.js eso es lo que ocurre. Bueno,
más o menos. Vas a tener que confiar en mi, un pequeño salto de fé. Si lo hiciste con Altair, ¡seguro
que puedes lograrlo!
La asincronía por naturaleza
En Node.js hay dos formas de hacer las cosas. Asíncronas o síncronas.

Valiente perogrullada… ¡No me aclaras nada!

Espera espera… Voy a ponerte un ejemplo. Imagina que vas a prepararte el desayuno. Tienes hambre.
Vas a hacer café y tostadas. En un mundo síncrono, meterías la tostada. Cuando saliera del tostador,
comenzarías a hacer el café. Cuando el café saliera de la cafetera, untarías la tostada de mantequilla.
Cuando terminaras calentarías algo de leche. Finalmente te podrías tomar el desayuno solo que,
probablemente, el café que te eches en la taza esté un poco frío y la tostada esté fría al untarle la
mantequilla.
En un mundo asíncrono la cosa cambia. Pones la cafetera y, mientras se está haciendo el
café, calientas la leche y pones la tostada a tostarse. Para cuando termines de hacer la tostada
probablemente ya tengas el café preparado y esté todo listo para tomarse.
Veámoslo ahora con un ejemplo de código:

Ejemplo 1: Código tradicional síncrono

1 var resultado = db.query("SELECT * FROM usuarios");


2 // Esperamos a los resultados
3 hacerAlgoConEllos(resultado); // Esperamos a que termine
4 hacemosOtraCosa();

Ejemplo 2: Código asíncrono

1 db.query("SELECT * FROM usuarios", function(resultados){


2 hacerAlgoConEllos(resultado); // Esperamos a que termine
3 });
4 // Esta función se ejecuta justo después de que se haya lanzado la consulta
5 // antes de obtener los resultados
6 hacemosOtraCosa();

Y… ¿realmente se ejecutan las cosas a la vez?

La respuesta es no.
La asincronía por naturaleza 4

Aquí está el engaño…

Te dije que confiaras en mi. En apariencia se ejecutan a la vez. Sin embargo, tras el telón, lo que
realmente pasa es que las callbacks quedan en una pila y se van ejecutando. Hasta que ese callback
termine, el resto tendrá que esperar. Lo que ocurre, es que el motor de Node.js es tan rápido y
normalmente no usamos procesos tan pesados que, en apariencia, se ejecutan a la vez.
La parte negativa, es que no sabemos tampoco en qué orden se ejecutan. Para los que les gustan
las explicaciones técnicas, lo que realmente ocurre es que JavaScript, tanto el cliente como en el
servidor, se ejecuta en un único hilo, por lo que es técnicamente imposible que haga dos cosas a la
vez. Pero confía en mi, esto no es ningún problema.
Esto puede resultar un poco confuso, especialmente al principio. Pero si me vas siguiendo no deberías
perderte en el camino de la asincronía.
Instalando Node.js
Para no aturdirte en demasía, vamos con algo ahora sencillito. Así vamos intercalando una de cal
y otra de arena. Vamos a ponernos manos a la obra y a instalar Node.js para poder empezar a
cacharrear que es lo que a todos nos gusta, ¿verdad? Como verás es un proceso realmente sencillo.

Instalando en Windows y Mac


El proceso de instalación en ambos sistemas es exactamente el mismo.

1. Nos dirijimos a la página de Node.js: http://nodejs.org/


2. Le damos al botón grande y verde que pone INSTALL. Esperamos.
3. Una vez finalice la descarga, tendremos un archivo .msi o .pkg depende del sistema en el que
estemos. Lo localizamos y hacemos doble click.
4. Finalmente, seguimos el proceso de instalación que es bastante sencillo.
5. Profit.
Instalando Node.js 6

Figura 1: Instalación de Node.js en Mac OSX

Instalando en Linux
Cubrir cada distribución de Linux es una locura, pero el proceso no debería variar mucho. En este
caso, vamos a ver cómo instalar Node.js en Ubuntu.

1. Nos dirijimos a la página de Node.js: http://nodejs.org/


2. Le damos al botón de DOWNLOAD.
3. En la siguiente página, tendremos que elegir el código fuente de Node.js que, en el momento
en que se escribe el libro, está en el último lugar de la tabla.
4. Una vez descargado, deberíamos colocarlo en la carpeta /usr/local/src.
5. Ahora hacemos algo como esto:
Instalando Node.js 7

Descomprimiendo e instalando Node.js

1 tar -zxf node-v0.10.5.tar.gz


2 cd node-v0.10.5
3 ./configure
4 make
5 sudo make install

Atención
Puede que necesites realizar sudo para realizar algunas acciones. Ten en cuenta además,
que el proceso puede llevar algo de tiempo. Si necesitas más información para instalar
Node.js en alguna versión de Linux en partícular, revisa las instrucciones de instalación
en la wiki de Node.js¹⁰.

¿Funciona mejor Node.js en algún sistema?


Ciertamente donde mejor funciona es en sistemas basados en Unix, sea este Linux o Mac OSX.
Instalar Node.js en Windows al principio era un peñazo, por no decir una palabra más fea. Había
que usar compiladores y armarse de paciencia.
Por suerte, Microsoft entendió que no se podía quedar atrás en esto, y se alió con Joyent (la empresa
detrás de Node.js) para portar Node.js a Windows como Dios manda.
Aun así, hay algunos paquetes que no funcionan en Windows porque tienen dependencias especiales
de Unix. Esto no quiere decir que no vayas a poder trabajar… ¡Para nada! De hecho, los ejemplos
que encuentres aquí los pruebo siempre en ambos sistemas por lo que no deberías preocuparte para
nada.

¿Qué acabamos de instalar?


Seguramente pienses que me he vuelto loco…

¿No estabamos instalando Node.js? ¿Por qué me haces esa pregunta?


¹⁰https://github.com/joyent/node/wiki/Installation
Instalando Node.js 8

La verdad es que sí, que estábamos instalando Node.js. Lo que no te he contado es que además,
acabamos de instalar npm. Esto ocurre de forma automática desde la versión 0.6.3 y es totalmente
transparente.
Ahora mismo no quiero que te preocupes demasiado sobre lo que es npm. ¡No te distraigas! Sigamos
la senda del conocimiento… Ohmmm…
Vamos a comprobar qué tenemos. Abre el terminal y escribe lo siguiente:

1 node --version

Si todo fue bien, deberás ver algo como:

1 v0.10.11

¡Bien! Ahora podemos seguir.

Accediendo a la consola
Tanto en Mac como Linux, la consola no es más que el terminal del sistema y suele llamarse Terminal
a secas. Especialmente en Linux estarás acostumbrado a usarla de cuando en cuando, pues muchos
programas y/o paquetes necesitan del uso eventual de la consola.
No obstante, en Windows es una versión modificada del cmd que nos crea directamente Node.js al
instalarla. Esta versión no tiene nada de especial, solo que nos añade algunas variables de entorno y
nos inicia automáticamente como administradores, de esta manera podremos ejecutar Node.js con
menos fricciones. Seguro que, con paciencia y saliva puedes hacer funcionar la consola normal de
Windows como lo hace la de Node.js. ¡Ánimo e inténtalo!
Para acceder a ella no tienes más que ir a la carpeta que el proceso de instalación habrá creado en
el menú de inicio:

Figura 2: Consola de Node.js en Windows


¡Hola mundo!
Antes de pasar a explicar nada más, y solo por seguir con la costumbre, vamos a crear nuestro
primer “programa” de Node.js y para ello vamos a ver dos formas de hacerlo. Si no te suena para
nada esta práctica, ¡debería! Lo que intentamos hacer es, que usando el lenguaje sobre el que estamos
aprendiendo, mostremos algo así como un saludo. Normalmente es Hola Mundo! o Hello World! en
Inglés.
Primero escribe node en la consola.
¿Ya? Si todo ha ido bien, debería quedarse con un símbolo > esperando nuestras órdenes. No pdoemos
darle cualquier orden, por ejemplo esto no funcionará:

1 > Di "Hola Mundo"


2 ...

Node no entiende nada y nos devuelve un …. Creo que es su forma de decirnos. ¿En serio? ¿Qué
quieres que haga con eso? Vamos a probar a hablarle en su idioma, JavaScript.

1 console.log('¡Hola mundo!');

Ahora sí, Node nos dirá algo porque es capaz de entendernos:

1 ¡Hola mundo!
2 undefined

¡Bien! ¡Ahora nos saluda! Pero… espera… ¿qué es eso de undefined? Bien. A ver cómo lo explico.
undefined es solo que la sentencia que acabamos de escribir, no “devuelve” ningún valor. Aunque
seguro que ya lo sabes, ehem, el valor undefined es como si no hubiera valor alguno. Si alguna vez
has usado la consola de las herramientas de desarrollador de Chrome, verás que el comportamiento
es exactamente el mismo.
Vamos a ver qué pasa si ponemos algo que sí devuelva un valor.

1 parseInt('30',10);

Aunque esto también seguro que lo sabes, parseInt intenta transformar una cadena en un número
y lo devuelve así que, en esta ocasión, veremos que nada más darle al Enter nos devuelve un 30.
Ahora que ya hemos probado esto rápidamente, veamos el otro método.
¡Hola mundo! 10

Pero… ¿¡cómo salgo de aquí?!

Upss… casi me olvido. Para salir, pulsa Ctrl + C (dos veces) y se cerrará la consola de Node. Esta es
la forma de “terminar un proceso”.
El otro metodo, como pronto descubrirás, es como lo haremos habitualmente, colocando el código
en un archivo. No creerías que ibas a tener que estar escribiendo ahí siempre, ¿verdad?
Lo que vamos a hacer, es crear primero una carpeta en la que guardaremos nuestra aplicación.

1 $ mkdir holamundo
2 $ cd holamundo/

Ahora que ya estamos dentro de la carpeta, y si estás en un sistema Unix, puedes hacer lo siguiente
para crear un archivo:

1 $ touch app.js

Si no, abre tu editor de texto favorito y crea un archivo en esa carpeta con ese nombre. El nombre
de app.js es solo una convención para indicar que es el archivo que contiene la aplicación y, por
tanto, el “ejecutable”.
Ahora abre el archivo con tu editor de texto preferido (si es que no lo habías hecho ya) y escribe
dentro lo siguiente:

1 console.log('¡Hola mundo!');

Guárdalo, y en el terminal escribe:

1 $ node app.js

Esta vez deberíamos ver el mensaje sin el undefined de antes y, tras aparecer, la aplicación se cerrará
sin más. ¡Fácil! ¿no?

Hola mundo… ¡en un servidor!


¿Recuerdas el ejemplo que pusimos en la introducción del libro? Vamos a revivirlo. ¡Vamos a crear
vida! Quita todo el código de app.js y escribe lo siguiente:
¡Hola mundo! 11

Hola mundo en el servidor

1 var http = require('http');


2
3 var server = http.createServer(function (request, response) {
4 response.writeHead(200, {"Content-Type": "text/plain"});
5 response.end("¡Hola Mundo!\n");
6 }).listen(8080);
7
8 console.log('Servidor escuchando por el puerto 8080');

Si ahora lo ejecutas con

1 $ node app.js

Verás que en la consola aparece el mensaje Servidor escuchando por el puerto 8080 que hemos
puesto en la línea 8. Ahora accede a http://localhost:8080 y, si todo ha ido bien, deberías ver una
página que dice ¡Hola Mundo!.
Veamos el ejemplo línea a línea:
En la primera línea lo que hacemos es require. Quizá puedas deducir lo que hace porque tenemos
un verbo bastante similar en castellano. Esta sentencia es la forma que tiene Node.js de cargar
los módulos. Es similar a include de PHP, #include de C o import en Python. Quizá te estés
preguntando porqué difiere de include si tanto PHP como C usan include. El motivo es que require
es una sentencia común cuando usamos require.js en el navegador para modularizar aplicaciones
JavaScript. Tampoco quiero profundizar mucho en esto porque puede ser un poco complejo.
En este caso, lo que estamos requiriendo es el módulo http que, casualmente, es un paquete del
núcleo de Node.js por lo que no necesita que lo instalemos.

Ten en cuenta que…

Aunque es buena idea llamar a las variables que reciben los módulos del mismo modo
que se llama el módulo, recuerda que es solo una convención para mantener la cordura.
Siempre puedes poner var ptth = require('http'); si quieres volver loca a la gente
que lea tu código.

Lo siguiente que hacemos es crear un servidor valiéndonos de la función createServer que nos
facilita http. Ésta recibe como parámetro una función, en este caso anónima, con dos parámetros
¡Hola mundo! 12

request, que es la petición, y response que es la respuesta que tenemos que enviar. Esa función será
llamada cada vez que alguien entre en la URL de nuestro servidor, así que tenemos que decirle lo
que tiene que hacer.
En nuestro caso, le indicamos que la petición debe tener un código 200, que es el código HTTP para
decir que la petición es correcta, y que el tipo de contenido que tiene que esperar el navegador es
texto plano. Además, finalizamos la respuesta con el método end, añadiendo el texto que queremos
devolver.

¿Qué pasaría si no llamáramos a end?

Si no llamáramos al método end de la respuesta, la petición nunca terminará y el


navegador acabará por dar un error porque sigue esperando que le enviemos datos. ¿No
me crees? ¡Pruébalo y verás!

Antes dije: “en este caso anónima”. Ten en cuenta que este código sería totalmente equivalente y
válido:

Función no anónima

1 var http = require('http');


2
3 function gestionaPeticion (request, response) {
4 response.writeHead(200, {"Content-Type": "text/plain"});
5 response.end("¡Hola Mundo!\n");
6 }
7
8 var server = http.createServer(gestionaPeticion).listen(8080);
9
10 console.log('Servidor escuchando por el puerto 8080');

O incluso este:
¡Hola mundo! 13

Función en variable

1 var http = require('http');


2
3 var gestionaPeticion = function (request, response) {
4 response.writeHead(200, {"Content-Type": "text/plain"});
5 response.end("¡Hola Mundo!\n");
6 }
7
8 var server = http.createServer(gestionaPeticion).listen(8080);
9
10 console.log('Servidor escuchando por el puerto 8080');

La función createServer recibe otra como parámetro y se encargará de ejecutarla cuando haya
terminado su trabajo. Esto se conoce como callback. Es un patrón muy común cuando se hacen
cosas asíncronas ya que, sabemos cuándo se comienza a ejecutar el código, pero no sabemos cuándo
va a acabar.
En Node.js, los callbacks por norma general tienen la siguiente estructura. El primer parámetro de
un callback debe ser un error si lo hubiere, en caso contrario null. A continuación, le siguen el resto
de parámetros.

Espera Antonio, ¿entonces Node.js no está siguiendo su propio patrón?

No es eso. Lo que realmente ocurre es que la función que estamos pasando más que un callback es
algo que está a la escucha para cuando llega una petición al servidor. El servidor tiene sus propios
callbacks internos a la hora de realizar tareas asíncronas y, si hay un error, se encargará de gestionarlo
él por lo que nuestra petición debería llegar sin más.
En la siguiente línea, le decimos por el puerto por el que escuchar, en este caso el 8080. Puedes elegir
cualquier otro puerto que no esté en uso. ¿Quién soy yo para decirte que no uses el puerto 6969 para
tu desarrollo? ¡Nadie!
Como verás, la llamada está encadenada con createServer. Eso es lo mismo que poner justo debajo…

1 server.listen('8080');

… puesto que createServer lo que devuelve es un objeto servidor.


¡Hola mundo! 14

¿Te has fijado?

Si eres observador habrás visto que detrás de createServer tenemos una llamada a la
función listen. Esto se conoce como encadenamiento de funciones y se logra devol-
viendo siempre el objeto creado, en este caso el servidor. Este tipo de comportamiento
se ha hizo especialmente común cuando apareció jQuery.
La consola de Node
Si habéis estado atentos, habréis visto que hemos estado usando console.log y que, la información
que hemos puesto, aparecía en el terminal mientras ejecutábamos la aplicación.
Si has trabajado con JavaScript (cosa que espero) quizá ya estés acostumbrado a console puesto que
es una forma de mostrar en la consola del Navegador información para ver por dónde va pasando
nuestro código.
console sirve para escribir en la salida estándar y en la de error. Ésta nos ayudará enormemente en la
tarea de saber qué es lo que está pasando en las entrañas de nuestra aplicación sin ser excesivamente
complicado como podría ser el hecho de activar un depurador.
Aunque no es algo de lo que, a priori, tengamos que preocuparnos, cabe destacar que escribir en
la consola es un método síncrono por lo que, si abusamos brutalmente de ella, estaremos creando
bloqueos en nuestra aplicación.
Hay librerías estupendas para registro de eventos y logging en general para Node, como winston¹¹.
No obstante, para el propósito de este libro nos valdremos de las bondades de la consola ya que cubre
todas las necesidades de aprendizaje.

console.log y console.info
Estas dos funciones hacen exactamente lo mismo, pasar información a la salida por defecto. La
primera es la que hemos usado en nuestro ejemplo de Hola mundo. No tiene excesivo misterio:

1 console.log('Gracias por hacerte con este libro');

No obstante, la consola se guarda un par de ases en la manga. Veamos uno de ellos. La consola nos
permite formatear la información al más puro estilo sprintf de PHP.
Imagina que quieres mostrar el número de planetas que hay en nuestro sistema solar, pero en vez
de poner un número ahí en plan soso, con una frase para dotarlo de sentido. Podrías hacer esto:

1 var planetas = 8;
2
3 console.log('Hay ' + planetas + ' planetas'); // Hay 8 planetas

Ya que lo que estamos pasando es, de hecho, una cadena de caracteres. No obstante, ¿no sería mejor
tener que evitarnos todos esos +? Yo creo que sí. Vamos a ver cómo lograrlo.
¹¹https://github.com/flatiron/winston
La consola de Node 16

1 var planetas = 8;
2
3 console.log('Hay %d planetas', planetas); // Hay 8 planetas

Lo que hacemos es pasarle a la consola una cadena, y parámetros extra que sustituirá en orden
de aparición. Ten en cuenta que si pasas más parámetros de los que realmente pones, Node.js
símplemente lo pondrá al final.

1 var planetas = 8,
2 expulsado = "Plutón";
3
4 console.log('Hay %d planetas',planetas,expulsado); // Hay 8 planetas Plutón

Vamos a arreglarlo, pasando un parámetro más:

1 var planetas = 8,
2 expulsado = "Plutón";
3
4 console.log('Hay %d planetas porque %s ya no es uno de ellos',planetas,expu\
5 lsado);
6 // Hay 8 planetas porque Plutón ya no es uno de ellos

Como ves, ahora hemos puesto la cadena en su sitio.

Vale, ¿pero qué es eso de %d y %s?

Me alegra que lo preguntes… ¡oh espera! Esos valores son patrones que podemos usar para sustituir
cadenas. ¡Hay más!

Patrón Tipo
%d Enteros y coma flotante
%s Cadenas
%j Objetos JSON

Vale, no es que sean muchos más, pero al menos ahora tenemos una idea.
Vamos a ver qué ocurre si le damos un objeto a la consola.
La consola de Node 17

1 var sistema_solar = {
2 planetas : 8,
3 expulsarPluton : function() {
4 ...
5 }
6 };
7
8 console.log(sistema_solar);
9 //{ planetas: 8, expulsarPluton: [Function] }

Como ves, intenta convertirlo a JSON y mostrarnos tanto sus propiedades como métodos para que
podamos evaluar, a simple vista, el valor del objeto.

console.error y console.warn
Estas funciones son equivalentes y hacen lo mismo que log e info salvo que en vez de a la salida
por defecto, lo envían a la de errores.

¿Qué es eso de la salida de errores?

La salida de errores es una salida alternativa. Esto permite más flexibilidad puesto que puedes tener
dos tipos de logs. Uno para errores (que serán importantes) y otro de información (más trivial). Esto
es especialmente útil cuando tienes multitud de mensajes mostrándose por tu aplicación y tú solo
quieres centrarte en los problemáticos.

console.time y console.timeEnd
Estas funciones las usaremos para hacer pruebas de rendimiento de nuestro código. La primera
función es como si agarráramos un cronómetro, lo pusiéramos a 0 y empezáramos a contar.

1 console.time('Operación costosa');
2 // Aquí realizamos una… operación costosa
3 console.timeEnd('Operación costosa');

timeEnd es como si paráramos ese cronómetro, miráramos el tiempo y lo anunciáramos al mundo.


Cuando se ejecute, veremos aparecer algo como:
La consola de Node 18

1 Operación costosa: 33ms

Esta es una forma realmente conveniente de saber dónde podemos tener algún cuello de botella en
nuestro código.
Accediendo a las variables del
entorno
Quizá ni siquiera sepas lo que es una variable de entorno. No te avergüences. Es una cosa que hoy
en día ya casi no se aprender ya que no son tan necesarias como antiguamente. No obstante, en
sistemas Unix aun se usan bastante en desarrollo. Si no sabes lo que son, voy a intentar definirlas.

Una variable de entorno es una variable que es almacenada por el Sistema Operativo y
que puede afectar a la forma en que se ejecutan los procesos del sistema.

Esto es, son variables que el sistema nos permite configurar y que pueden modificar cómo actúa en
sí el sistema.
Una de las variables más conocidas es el PATH, en la que se almacenan las rutas de los ejecutables.
Si una ruta está definida en esa en los sistemas Unix por ejemplo. Si tienes una ruta en esa variable,
podrás ejecutar una aplicación sin necesidad de ir a donde está ubicado.
Definir una variable de entorno es realmente sencillo:

1 // Unix
2 NUESTRA_VARIABLE="Su valor"; EXPORT NUESTRA_VARIABLE
3 // Windows
4 set NUESTRA_VARIABLE="Su valor"

En sistemas Unix tenemos además la oportunidad de crear una variable que solo será visible dentro
de Node.js, en esa ocasión que la lancemos.
Lo más habitual es establecer la variable NODE_ENV, que establece el tipo de entorno en el que estamos
trabajando que normalmente será production (producción) o development (desarrollo):

1 NODE_ENV=production node app.js

Todas las variables del entorno están accesibles desde el objeto env, que forma parte del objeto
process. De esta manera, si queremos por ejemplo cambiar el puerto en el que vamos a ejecutar
nuestra aplicación, en función del entorno en el que estemos, haríamos algo así:
Accediendo a las variables del entorno 20

1 var puerto = 80;


2
3 switch(process.env.NODE_ENV) {
4 case 'production':
5 puerto = 8080;
6 break;
7 case 'development':
8 puerto = 8888;
9 break;
10 }

¡Recuerda!

Las variables de entorno definidas de esta forma son temporales y, o bien desaparecen al
cerrar la aplicación, o bien al reiniciar el equipo. Si quieres añadir las variables de forma
permanente, tendrás que valerte de ∼/.bash_profile en sistemas Unix o cambiarlas en
las propiedades del sistema en un entorno Windows.
Pasando parámetros a Node.js
Además de valernos de las variables del entorno, podemos pasarles parámetros a Node.js de la
siguiente forma:

1 $ node app.js [argumentos]

Este tipo de técnicas es especialmente útil si, en vez de crear un servidor, creamos algún tipo de
aplicación que haga un proceso en función de uno o varios parámetros. Por ejemplo, imagina un
pequeño script que compare dos ficheros de texto y cree un tercero con las diferencias. En ese
caso, podrías pasar los dos nombres de los ficheros a comparar y, un posible tercer parámetro,
que contenga el nombre del archivo diferencial que se genere Esos argumentos van separados por
espacios y quedan almacenados dentro del objeto argv, que forma parte de process. No obstante,
hay dos valores fijos dentro del objeto:

1. node
2. El nombre del script que estamos ejecutando. Por ejemplo:
/Users/antonio.laguna/proyectos/nodetest/app.js

En la mayoría de las ocasiones, estos argumentos no nos interesan para nada. Por ello, podríamos
hacer algo así:

1 var argumentos = process.argv.splice(2);

Por si no estás familiarizado con splice, lo que hace es en ese caso recortar la matriz y dejar lo que
queda a partir del elemento 2. Dado este código:

1 var argumentos = process.argv.splice(2);


2 console.log(argumentos);

Si llamáramos a la aplicación de esta forma: node app.js uno dos tres el resultado sería:

1 [ 'uno', 'dos', 'tres' ]


Pasando parámetros a Node.js 22

Ya que ha eliminado los dos primeros iniciales, que no nos interesaban.

De interés…
En caso de que quieras crear un programa de consola, al que le puedas pasar varios
parámetros, un buen módulo que he usado en alguna que otra ocasión con bastante
éxito es Optimist¹². Una librería que te permite parsear opciones, añadir alias, parámetros
obligatorios, menú de ayuda automático, etc.

¹²https://github.com/substack/node-optimist
NPM - Node Packaged Modules
Como ya hemos comentado, npm es instalado con Node.js de forma automática. Sí, sin que tengamos
que hacer nada de particular. Lo bueno es que cuando actualicemos Node.js, npm lo haga también
de forma automática.

Vale Antonio, ¿pero me vas a explicar qué es eso de npm?

npm es el gestor de paquetes de Node. Nos sirve para realizar cualquier actuación con paquetes
de node. Buscarlos, instalarlos, actualizarlos, etc. Es posible que hayas utilizado algún gestor de
paquetes con anterioridad. Los hay para varios sistemas:

• PHP - Composer
• Python - Pip
• Ruby - RubyGems
• OS X - Homebrew

Vamos a ver cómo podemos hacer algunas de estas operaciones con npm. Especialmente las más
útiles.

Búsqueda de paquetes
En ocasiones querremos buscar algo. No sabemos cómo se llama el módulo pero sabemos que
queremos algo para gestionar… uhmm… ¡asincronía! Si el programador del paquete lo ha hecho bien,
debería haber escrito esa palabra en las palabras claves. Dado que somos unos frikis sin remedio,
vamos a buscarlo desde la consola. Para ello no tenemos más que escribir npm search {palabra a
buscar}. Por ejemplo:

1 $ npm search async

Esto comenzará a aparecer por pantalla:


{lang=text ∼∼∼∼∼∼∼ NAME DESCRIPTION
abiogenesis Asyncronous, (…) actor Experimental (…) advisable Functional (…) aegis Asynchronous
(…) aejs Asynchroneous (…) aemitter async emitter (…) ajs Experimental (…) ake A build tool (…)
alf Asynchronous (…) ∼∼∼∼∼∼∼
Como ves, la lista no es de gran ayuda. Ten en cuenta que esto nos soltará una lista de todos los
paquetes que contengan esa palabra en su descripción y/o nombre, por lo que lo mejor es saber de
NPM - Node Packaged Modules 24

antemano qué vamos a usar. Pero, por lo menos, hemos satisfecho nuestro orgullo friki y hemos
buscado paquetes desde la línea de comandos. ¡Estás hecho un hacker!
Aunque no sea tan emocionante, puedes hacer la misma búsqueda en https://npmjs.org/ ya que es
donde npm search busca sus paquetes

Obtener información de paquetes


Ahora que tenemos una lista de paquetes, seguro que quieres saber algo más de los paquetes. Yo,
que soy muy curioso, tengo ganas de saber qué hace el paquete abiogenesis. No es que lo conozca,
lo prometo. Es que es un nombre rarísimo. Para saber los detalles de un paquete, escribiremos npm
view {nombre del paquete}.

1 $ npm view abiogenesis

Esto realmente nos devuelve una visualización del archivo package.json. No te preocupes mucho
por él ahora mismo, lo veremos en breve. Ya sé que tienes curiosidad, así que te voy a poner un
pequeño fragmento:

1 { name: 'abiogenesis',
2 description: 'Asyncronous, nested \'task\' runner framework with dependen\
3 cy resolution.',
4 'dist-tags': { latest: '0.5.0' },

Vaya, ¡es un fantástico framework que ejecuta tareas asíncronas y anidadas con resolución de
dependencias! ¿Tendrá ziritione?

Lista de paquetes instalados


En ocasiones querrás ver la lista de paquetes que tienes instalados en tu aplicación. Para ello, solo
tenemos que ejecutar un sencillo comando:

1 $ npm ls

Y veremos algo así:


NPM - Node Packaged Modules 25

1 /Users/antonio.laguna/projects/nodetest
2 └─┬ less@1.3.3
3 └── ycssmin@1.0.1

Esta lista nos muestra los paquetes en forma de árbol. Como ves, nos muestra que en mi proyecto
tengo instalado less, cuya versión es 1.3.3. Abajo, tenemos otro paquete, ycssmin. Este paquete,
por el gráfico, podemos saber que es una dependencia de less. Esto es, less necesita a nycssmin para
funcionar. Como veremos ahora, cuando instalamos un paquete, npm se trae todas sus dependencias.
Ahora también hablaremos de los paquetes globales. Basta decir con que si quieres ver una lista de
todos los paquetes que tienes instalados a nivel global, solo tienes que hacer algo así:

1 $ npm ls -g

Y verás todos los paquetes globales que tienes instalados, con sus dependencias por supuesto.

Instalación de paquetes
Antes de meternos de lleno a instalar paquetes como cosacos, vamos a hablar sobre el tipo de
instalaciones. Ya las hemos dejado caer un poco en la anterior sección pero… allí vamos. Tenemos
dos tipos de instalaciones: La instalación global y la instalación local.
Veamos la diferencia.
Los paquetes locales son aquellos que se instalan sobre la aplicación en la que estás trabajando en
el momento en que ejecutas el comando. Si por lo que sea la instalas en una carpeta que no debías,
se instalará igualmente puesto que npm no distingue si estás o no en una aplicación. Los paquetes
locales solo estarán disponibles en el paquete actual. Normalmente es la forma más habitual de
instalar un paquete.
Para instalar un paquete localmente, solo tenemos que ejecutar el siguiente comando npm install
{nombre del paquete}. Por ejemplo:

1 $ npm install less

Ahora tu proyecto, podrá hacer var less = require('less'); y comenzar a usar sus funciones.
Ten en cuenta que no puedes usar paquetes externos que no hayas instalado.
Por el contrario, habrá veces que el paquete venga con funcionalidad extra o, directamente, sea un
programa de la línea de comando. En esos casos tendremos que instalarlo de forma global. Hacerlo
es realmente sencillo ya que únicamente tenemos que añadir un distintivo : npm install -g {nombre
del paquete}. Por ejemplo:
NPM - Node Packaged Modules 26

1 $ npm install less -g

Ten en cuenta que el instalar un paquete de forma global no hace que éste esté disponible dentro de
todas las aplicaciones usando require().

Cómo decidir qué tipo de instalación realizar


Piensa que, en general, una instalación global no es algo bueno. Seguro que, trabajando con
JavaScript has leído que las variables globales son malas. Las instalaciones globales… también.
Bueno, no es que sean malas, solo que es algo que no tendríamos que tomar a la ligera.

Entonces, ¿cómo puedo decidir?

Veamos unos consejos:

• Si quieres instalar un paquete que funcione dentro de tu aplicación con require(), instálalo
de manera local.
• Si estás instalando un paquete que puede ser usado desde la línea de comandos como un
programa o algún uso extendido (como en el caso de Express), instálalo de manera global.

Algunos de los paquetes que suelen instalarse de manera global:

• Express
• forever
• nodemon
• Bower
• Grunt
• Uglify-js

Webs de instalación de paquetes


Algunos usuarios han decidido mejorar las, ehem, bondades de npm search creando algunas
herramientas realmente útiles.
Una web realmente útil a la hora de instalar paquetes es Nipster¹³. Esta herramienta te permite
visualizar un ranking de los paquetes en Github, permitiéndote ordenarlos por Fecha de Modificación
(útil para saber si siguen o no activos), número de forks, estrellas, etc.
Como alternativa a Nipster, encontramos Gitmoon¹⁴, que nos ofrece mucha más información
de la que podamos encontrar en Nipster sobre cualquier paquete que queramos buscar como la
dependencia entre proyectos, quién usa el paquete, etc.
Si descubres (¡o creas!) alguna web que creas que puede cumplir con estas funciones, ¡házmelo saber!
¹³http://eirikb.github.io/nipster/
¹⁴http://www.gitmoon.com/
NPM - Node Packaged Modules 27

Desinstalación de paquetes
Desinstalar un paquete es tan sencillo como instalarlo. Durante el desarrollo es normal probar
paquetes, puede que luego no te guste, no cumpla con tus expectativas o, directamente, no lo necesites
y lo hayas instalado porque tenía un nombre divertido. Yo lo sé porque me lo ha contado un amigo.
El comando es bastante sencillo… npm uninstall {nombre del paquete} o npm uninstall -g
{nombre del paquete} si queremos desinstalarlo globalmente. Por ejemplo:

1 $ npm uninstall -g less

Esto hará que el paquete sea borrado de la faz de nuestro equipo. Así, sin más.

Paquetes útiles y habituales


• nodemon¹⁵ - Realmente útil durante el desarrollo ya que relanza la aplicación con cada cambio
que hagamos en el archivo, evitando tener que lanzar y detener el proceso contínuamente.
• mongoose¹⁶ - Si trabajas con mongoDB¹⁷, este es sin duda uno de los módulos que querras
usar para guardar tus objetos ya que su sintaxis es realmente sencilla y fácil de comprender.
• node-mysql¹⁸ - Si eres más tradicional y no te apuntas al carro de NoSQL, este conector contra
MySQL funciona realmente bien.
• Nodemailer¹⁹ - Si lo que quieres es enviar correos, Nodemailer te lo pone fácil. Este módulo
permite SMTP, Amazon SES y la función sendmail que tenga el sistema. En su página
encontrarás ejemplos sobre cómo hacerlo funcionar con Gmail por ejemplo.
• node-validator²⁰ - Validar campos de un formulario es una tarea que, más tarde o más
temprano, acabaremos realizando. Este módulo nos ayuda a validar campos y a sanearlos
eliminando caracteres blancos, escapándolos, etc.

¿Crees que hay algún paquete que deba estar aquí? ¡Házmelo saber!

Dudas frecuentes

¹⁵https://github.com/remy/nodemon
¹⁶http://mongoosejs.com/
¹⁷http://www.mongodb.org/
¹⁸https://github.com/felixge/node-mysql
¹⁹https://github.com/andris9/Nodemailer
²⁰https://github.com/chriso/node-validator
NPM - Node Packaged Modules 28

¿Cómo actualizo un paquete?


Actualizar un paquete es realizar la misma tarea de instalación, es decir npm install {nombre del
paquete} o npm install -g {nombre del paquete} si queremos actualizarlo globalmente. Asegúrate
de revisar las notas de la versión antes de realizarlo, especialmente si es una aplicación en producción,
ya que puede que hayan eliminado funciones o cambiado el funcionamiento de algo.
.
Cómo mantener Node.js actualizado
Si has leído al principio del libro, ya sabrás que Node.js se actualiza con bastante frecuencia. A veces,
incluso un par de veces a la semana.
La pregunta lógica que surge es… ¿cómo mantengo Node.js actualizado?
Ciertamente, podrías seguir los mismos pasos que hemos visto a la hora de instalar Node.js. Pero,
si eres como yo, te parecerá un rollazo. No obstante, hay un método mejor y que yo suelo usar con
bastante asiduidad.
El paquete se llama n y tendremos que instalarlo de forma global ya que, en el fondo es un programa
de la línea de comandos.

1 $ npm install -g n

Una vez que tenemos instalado n ya podemos comenzar a usarlo. Para actualizar la versión de Node.js
solo tendremos que escribir:

1 $ n stable

Y él solo se encargará de hacer el resto. ¿No es genial? A mi personalmente me ahorra bastante


tiempo. Estoy suscrito por Twitter a la cuenta de Node.js²¹ y, si leo que tienen una actualización solo
tengo que lanzar el comando de arriba, esperar un ratillo y disfrutar de una nueva versión.
Lamentablemente n no funciona con Windows. Más lamentablemente aun, en su Github no aparece
ninguna incidencia al respecto por lo que parece que no tienen interés en arreglarlo.
n tiene otros comandos que te pueden servir:

Comando Acción
n latest Instala o activa la última versión de node
n stable Instala o activa la última versión estable de node
n <version> Instala y/o usa la <version> de node especificada
n use <version> [args ...] Ejecuta la <version> con [args ...]
n bin <version> Muestra la ruta de los ejecutables de la <version>
n rm <version> Elimina la(s) version(es) especificada(s)
n --latest Muestra la última versión disponible de node
n --stable Muestra la última versión estable de node
n ls Muestra las versiones de node disponibles

²¹https://twitter.com/nodejs
Cómo mantener Node.js actualizado 30

Personalmente he usado a veces n <version> ya que en ocasiones, me han colado como “estable”
una versión y luego resultaba que me habían roto algo que no me dejaba funcionar correctamente.

Sobre las versiones de Node.js


Cuando aparece una nueva versión de Node.js, aparece una noticia en el blog de Node.js o en su
Twitter. Concretamente puedes dirigirte al apartado [release] (http://blog.nodejs.org/release/) para
ver qué es lo que han sacado.
Cada versión viene con la cadena al lado (Stable) o (Unstable) junto a una escueta explicación de los
cambios introducidos. En la mayoría de las ocasiones no nos aportará mucha información ya que
suelen ser mejoras de rendimiento y errores corregidos. Pero tendrás que dar un salto de fe. Pensar
que lo estable es realmente estable… e instalarlo.
A veces, cuando ocurre un cambio de versión mayor como el que ocurrió al saltar de la versión 0.8
a la 0.10, podemos encontrar un artículo “mucho” más detallado²² e incluso una página en GitHub²³
donde nos detallan las diferencias entre versiones.
¡Recuerda visitar siempre el blog de Node.js antes de realizar ninguna actualización de versión!
²²http://blog.nodejs.org/2013/03/11/node-v0-10-0-stable/
²³https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
Nuestra primera aplicación de
Node.js
Ahora que ya tenemos conocimientos básicos, vamos a ponernos manos a la obra… ¿no? Recuerdo
cuando empecé a estudiar programación y todo el mundo le preguntaba a la profesora: ¿cuándo
vamos a poder programar? ¿para qué tanta teoría? Así que, no me enrollo más, pongámonos manos
a la obra.
Vamos a hacer una sencilla calculadora, nada del otro mundo, pero nos ayudará a ir abriendo boca
y a aplicar casi todo lo que hemos visto hasta ahora. Ten en cuenta que este no es el objetivo de
Node.js pero no quiero complicar mucho este primer capítulo.

Código de la calculadora

1 var argumentos = process.argv.splice(2),


2 operacion = argumentos[0],
3 valor1 = parseInt(argumentos[1],10),
4 valor2 = parseInt(argumentos[2],10),
5 nombreOperacion, resultado;
6
7 if (operacion === undefined || valor1 === undefined || valor2 === undefined\
8 ) {
9 console.error('Alguno de los parámetros no ha sido especificado.');
10 }
11 else {
12 switch (operacion) {
13 case '*' : {
14 nombreOperacion = 'multiplicación';
15 resultado = valor1 * valor2;
16 break;
17 }
18 case '+' : {
19 nombreOperacion = 'suma';
20 resultado = valor1 + valor2;
21 break;
22 }
23 case '-' : {
24 nombreOperacion = 'resta';
25 resultado = valor1 - valor2;
Nuestra primera aplicación de Node.js 32

26 break;
27 }
28 case '/' : {
29 nombreOperacion = 'división';
30 if (valor2 !== 0){
31 resultado = valor1 / valor2;
32 }
33 else {
34 console.error('¿Qué querías? ¿¡Cargarte el universo?!')
35 }
36 break;
37 }
38 default : {
39 console.error('La operación %s no ha sido implementada', operacion);
40 break;
41 }
42 }
43
44 if (resultado !== undefined){
45 console.log('El resultado de la %s es %d', nombreOperacion, resultado);
46 }
47 }

Como dijo Jack, vayamos por partes.


En el primer bloque declaramos todas nuestras variables. Es habitual usar las primeras líneas para
este cometido. Como ves, nos hacemos con todos los argumentos que le hayamos pasado a la
aplicación, como hemos aprendido y declaramos algunas variables vacías que contendrán valores
una vez que realicemos la operación en sí.
En caso de no obtener alguno de los tres parámetros que necesitamos (dos valores y la operación)
lanzaremos un error para que el usuario sepa que no lo está haciendo correctamente.
Gracias a un switch, encauzaremos nuestra aplicación por el buen camino. Como puedes ver hemos
implementado 4 operaciones: suma, resta, multiplicación y división. En caso de que no sea ninguna
de ellas, devolveremos un error.
Como verás, no hacemos comprobaciones excepto para la división, para evitar divisiones entre cero
en cuyo caso, devolvemos un error. Todos somos guardianes del universo. Protegerlo está en manos
de todos.
Finalmente, en caso de que tengamos un resultado (!== undefined), mostramos la operación y su
resultado, dando formato a la consola.
Hagamos un par de pruebas:
Nuestra primera aplicación de Node.js 33

1 node app.js '*' 3 4


2 // El resultado de la multiplicación es 12
3 node app.js '+' 997 3
4 // El resultado de la suma es 1000
5 node app.js '^' 2 2
6 //La operación ^ no ha sido implementada

¡Parece que funciona! Nada difícil hasta ahora, ¿no?

Ejercicio

Intenta añadir una nueva operación: la potencia en el que el primer parámetro sea la
operación, el segundo el valor a elevar y el tercero la potencia que queremos usar. ¿Hay
que hacer alguna validación especial?
Resumen
Hasta aquí nuestro primer capítulo. Si has seguido hasta aquí el libro, habrás aprendido cuál es la
esencia de una aplicación de Node.js y cómo funciona la asincronía. Si no has terminado de entender
el concepto, te recomiendo que le vuelvas a echar un vistazo porque es de las cosas más importantes
de Node.js y es necesario que el concepto quede claro.
Después, hemos visto cómo hacer uso de la consola de node para imprimir texto y mostrar contenido
de los objetos a través de los métodos que nos ofrece console. Hemos visto la diferencia entre la
salida normal y la de errores y cómo medir el tiempo con la consola. Sin duda métodos muy útiles
para ver qué es lo que va ocurriendo en nuestra aplicación
Adicionalmente, hemos visto cómo podemos valernos de las variables del entorno para poder
modificar el comportamiento de nuestra aplicación en función del valor de alguna de las variables.
Además, has aprendido a pasar parámetros a tu aplicación de Node.js para ayudar a que tu aplicación
funcione.
Además, habrás aprendido a instalar Node.js y npm y cómo interactuar con la consola de Node. npm
nos ofrece muchas opciones que no hemos dudado en ver una a una, como instalar o desinstalar
paquetes, cómo actualizarlos y la diferencia entre instalaciones globales y locales de paquetes.
Finalmente, hemos creado una sencilla calculadora con todo lo que hemos aprendido en este capítulo
y nos hemos asegurado de que funciona correctamente.
En el siguiente capítulo, veremos temas más avanzados como exportación/importación de ficheros
en nuestra aplicación con require, organización de los archivos de nuestra aplicación, emisión de
eventos y más cosas que tienes por descubrir. ¡No te las pierdas!
Adentrándonos en Node.js

Ahora que ya tenemos los conceptos básicos sobre Node.js, podemos empezar a jugar con conceptos
y funciones que son más complejas. Estoy seguro de que es lo que estabas deseando, ¿verdad? A nadie
le gustan las presentaciones, son aburridas y odiosas pero, al menos en este caso, estaba totalmente
justificada. ¡Lo prometo!
Este capítulo podemos considerarlo como la planta baja de nuestra casa, ya que sobre todos estos
fundamentos comenzaremos a profundizar en la tercera parte cuando nos metamos en faena con
Express.
Basta de cháchara. ¡Vamos a ponernos manos a la obra!
Gestión de dependencias con
package.json
Node.js implementa CommonJS para distribuir paquetes lo cual ayuda a que todo este proceso esté
bastante extendido. Esto hace que, si creas una aplicación, la puedas distribuir fácilmente a través
de npm para que pueda ser instalada por cualquiera.
Pero hay una cosa que tienes que tener en cuenta. Tu aplicación puede depender por ejemplo de
Express, pero Express a su vez, tiene una serie de dependencias que necesita para funcionar, que a
su vez… ¡pueden necesitar más dependencias!
No obstante, queremos evitar a toda costa el tener que bajarnos las dependencias manualmente y,
lo que es aun peor, sus versiones. Es por ello, que a la hora de distribuir un paquete a través de npm,
viene sin la carpeta node_modules en la cual se instalan todas las dependencias.
Es probable que te estés preguntando…

Si Express usa como dependencia a connect, ¿puedo hacer yo require('connect')y


usarlo yo también?

Realmente no. Tampoco es buena idea. La gestión de dependencias en cadena que usa Node.js (y la
mayoría de los lenguajes), está pensada así para que las versiones no den conflictos entre si. Voy a
intentar explicarlo con un ejemplo más gráfico.
Imagina por un momento, que eres carpintero. La programación es muy complicada y has decidido
buscar fortuna con otra profesión. Te has bajado de Google un diseño de rueda de carruaje
espectacular así que, diseñas tu carruaje alrededor de ese diseño de rueda, para que encaje
perfectamente. Hasta ahora todo bien.
Pero decides llevar tu carruaje a una feria para mostrarlo a posibles compradores. En el camino, se
te rompe una rueda del carruaje. Un compañero que pasa cerca, se ofrece a prestarte una de sus
ruedas. Desmontas la tuya y… nada. No entra la nueva rueda. Tu carruaje depende de la versión
de la rueda que te descargaste de Google. La otra no funciona. Ciertamente podrías hacer algunos
ajustes a tu carro y usar la otra rueda, pero no es lo ideal.
Por ello, si realmente quieres, puedes usar connect de Express usando algo así:

1 require('./node_modules/express/node_modules/connect')

Pero si el día de mañana, Express actualiza su versión de connect es muy probable que tu aplicación
deje de funcionar correctamente por lo que, lo mejor, es especificar la versión que quieres usar en
tus dependencias.
Gestión de dependencias con package.json 37

En el archivo package.json es donde colocamos toda esta información además de más cosas sobre
nuestro paquete. Quizá por el nombre hayas descubierto que el archivo tiene una estructura JSON.
No obstante, antes de meternos de lleno, tenemos que hablar de otra cosa.

Versionado semántico
Antes de meternos con el archivo en sí, me gustaría hablar del versionado semántico (semver), que
es usado tanto por Node.js como por los paquetes y tendremos que usarlo en cuanto definamos las
dependencias. Siguiendo nuestro ejemplo, sería la forma de catalogar la versión de las ruedas.
Cuentan las leyendas, que en tiempos ancestrales no existía esta forma de versionar el software.
Todos recordaremos la famosa etiqueta Beta de Gmail que, en aquel momento quería decir: “¡Esto
funciona pero vamos a añadir más cosas y eventualmente puede romperse!” (eso no es lo que hoy en
día significa Beta).
Además, teníamos las versiones Alpha que estaban en un estado anterior a Beta. No obstante, es
útil porque permite a la empresa probar el software y ofrecer feedback sobre cosas como Interfaz,
Velocidad y Usabilidad.
Hoy en día, las cosas han cambiado un poco conforme las metodologías han ido avanzando. Por
ello, también lo ha hecho la forma en que ponemos las versiones de las versiones (válgame la
redundancia). Hoy en día, lo que está de moda son las versiones del tipo x.y.z. Si no te gusta la
moda no te preocupes. Esta moda ha llegado por un motivo y es el hecho de darle significado a las
versiones (de ahí el nombre de versionado semántico).

• x - Cambio mayor : Este tipo de cambio es un cambio serio en la aplicación de manera que
es drásticamente diferente de anteriores versiones. Normalmente, si esta fuera una aplicación
de pago, es el tipo de cambio que te costarían dinero.
• y - Cambio menor : Has añadido algo nuevo que mejora significativamente el producto o
bien has cambiado algo que estaba causando muchos problemas y/o era malicioso o si estás
eliminando algo. Han de ser retrocompatibles con lo anterior.
• z - Parche : Correcciones de errores. Han de ser retrocompatibles con lo anterior.

En Node.js y en los paquetes, esta es la metodología que se usa para versionar cada uno de los
paquetes que podemos instalar con npm. No obstante, aceptan una modificación, el - por lo que
este tipo de versión 0.30.1-2 es válida para paquetes npm, pero no en el mundo semver. A mi
personalmente este añadido me parece superfluo ya que, nada te impide aumentar la versión z para
hacer ajustes, ¿no?
En general, no deberías usar 1.X.X a no ser que la aplicación sea estable. Seguro que dudas de si tu
aplicación es lo suficientemente estable como para ponerle ese 1.0.0 que tanto gusta. Quizá algo que
te ayude es hacerte esta otra pregunta:

¿Está la aplicación en producción?


Gestión de dependencias con package.json 38

Si has respondido sí, es que ya debería haber pasado a esa versión así que… ¡Corre!

¿Y no está Node.js en producción?

Hombre… no nos pongamos quisquillosos. En realidad, Node.js no es que esté en producción si no


que se usa para aplicaciones que se ponen en producción. No obstante, podríamos considerarlo como
“en producción”. Lo que ocurre, es que está en continuo cambio y hay muchas partes del código que
aun no funcionan todo lo estable que le gustaría al equipo que está detrás de su desarrollo.

¿Y qué pasa con las versiones Alpha y Beta ahora?

No te preocupes, aun no han desaparecido. Puedes usarlas si es que quieres y, en cierta manera, le
añaden al código más significado. Para crearlas, solo tienes que añadir al final la versión que quieras.
Por ejemplo 0.1.2-3-beta o 0.1.2-4-alpha. Aquí tienes un ejemplo de cómo evalúa node semver
las versiones:

1 0.1.2-7 > 0.1.2-7-beta > 0.1.2-6 > 0.1.2 > 0.1.2beta

Recuerda que NPM siempre intenta escoger la versión más actualizada del paquete disponible,
mientras no seas más específico.

Descubre más
Si te interesa saber más sobre la especificación, puede que quieras echar un vistazo a lo
que te cuentan en su página web (en inglés) ¡No te lo pierdas! - http://semver.org/²⁴

Estructura del archivo


Hay dos elementos que son imprescindibles en un archivo package.json. Sin ellos, no funcionará
bien. Especialmente con versiones antiguas de Node.js. El primer elemento es el nombre del paquete.
Este nombre no puede contener espacios vacíos ni caracteres extraños. Sí, la Ñ cuenta como caracter,
¡puñetas!
El segundo elemento no es otro que la versión del paquete que estamos creando. Puedes usar por
ejemplo la 0.0.1 para empezar.
No obstante, la tarea que más nos preocupa en este capítulo, no es otra que gestionar las dependencias
de nuestra aplicación. Así que, añadamos a la lista de “imprescindibles”, la dependencia de módulos
(con sus versiones) que necesitamos para usar el paquete.
Veamos un breve ejemplo:
²⁴http://semver.org/
Gestión de dependencias con package.json 39

1 {
2 "name": "mi-aplicacion",
3 "description": "Un paquete de prueba que nunca verá la luz",
4 "author": "Uno que aprende <megusta@aprender.com>",
5 "version" : "0.0.1",
6 "dependencies": {
7 "express": ">=3.2.0"
8 },
9 "engine": "node 0.10.x"
10 }

Como ves, los parámetros son bastante sencillos de entender. Todos estos parámetros son válidos en
este archivo y, lo que hacen, es dotar de más significado al paquete en sí. Si fueramos a distribuir el
paquete para que lo pudieran descargar terceros, sería sin duda buena idea el añadir estos parámetros.
Como ves, le estamos facilitando un nombre, una descripción, un autor (ficticio) y… las dependencias.
Como habrás observado, las dependencias están especificadas con notación semver.
Quizá te hayas fijado en que la dependencia que hemos indicado de Express es de la versión 3.2.0
o mayor.
Para instalar las dependencias solo tenemos que ejecutar el siguiente comando:

1 npm install

Y verás como automáticamente empieza a bajarse las dependencias indicadas. En este caso solamente
Express. Node intenta buscar la versión más actualizada posible que cumpla con los requisitos que
hemos puesto para cada paquete. Si por lo que sea no hay versión del paquete que cumpla con los
requisitos, la instalación fallará.
Aunque hayamos indicado que queremos las versiones mayores a 3.2.0 esto no significa que sea
una buena práctica. Realmente estamos diciendo, quiero estar a la última y, cada vez que ejecutemos
el anterior comando nos traeremos la última versión de la dependencia… genial, ¿no?

No

El día que por algún casual salga Express 4.0.0 es muy probable que tu aplicación deje de funcionar
porque los cambios no tienen por qué ser retrocompatibles. Así que recuerda tener mucho cuidado
con todo esto. Recuerda que…

Las versiones sin control, no sirven de nada…


Gestión de dependencias con package.json 40

Dependencias para desarrollo


Imaginemos que queremos, por algún casual, instalar dependencias que luego no vayamos a instalar
en producción, pero además, queremos que si alguien nuevo entra a trabajar en el proyecto, pueda
disponer de ellas correctamente. ¿Que no se te ocurre nada? Veamos… Quizá esto no te suene, pero
aunque es un tema que no vamos a tratar en el libro, la gran mayoría de este tipo de dependencias
son para instalar software que nos permita realizar pruebas sobre nuestra aplicación.
Para ello, podemos añadir un parámetro más a nuestro archivo package.json:

1 {
2 "name": "mi-aplicacion",
3 "description": "Un paquete de prueba que nunca verá la luz",
4 "author": "Uno que aprende <megusta@aprender.com>",
5 "version" : "0.0.1",
6 "dependencies": {
7 "express": ">=3.2.0"
8 },
9 "devDependencies": {
10 "mocha": "1.11.X"
11 }
12 }

Para instalarla, bastará con hacer el mismo npm install ya que, por defecto Node.js entiende que
estamos en desarrollo. Si quieres evitarlo no tienes más que ejecutar el siguiente comando:

1 $ npm install --production

Por otro lado,si la variable de entorno NODE_ENV tiene el valor de production, los paquetes de
desarrollo no se instalarán. ¿Y qué pasa si eres un vaquero y quieres instalar las dependencias de
desarrollo en un entorno que está marcado como production? Pues tienes otra opción:

1 $ npm install --dev

Instalar un paquete y guardarlo como dependencia


Como aprenderás si sigues disfrutando de las bondades de Node.js, npm es un buen colega tuyo. Uno
de esos que se preocupa de ti y te echa un cable cuando puede. Conforme avances en tu desarrollo,
a menudo te encontrarás con un problema similar a este:
“Vaya, quiero instalar un conector contra una base de datos MySQL. Voy a buscar algo en Google…
Humm este paquete de node-mysql parece ser lo que necesito. ¡Vaya, ahora tengo que actualizar mi
package.json!”
¡No! npm nos ofrece un par de atajos para guardar nuestros paquetes tanto como dependencias
normales como de desarrollo:
Gestión de dependencias con package.json 41

1 $ npm install node-mysql --save


2 $ npm install mocha --save-dev

Estas marcas opcionales, nos permiten actualizar automáticamente nuestro archivo package.json.
--save se encargará de guardarlo en la sección de dependenciesmientras que --save-dev lo
guardará en devDependencies.
¡Esto si que es un gran amigo!

Descubre más
Si te interesa saber más sobre el archivo package.json, puedes encontrar una guía
interactiva en la que tratan todos los campos que se pueden usar (muchos más de los
que hemos visto aquí) en la página de Nodejitsu - ¡No te la pierdas!²⁵

²⁵http://package.json.nodejitsu.com/
Exportando en Node.js
Hasta ahora hemos estado importando “paquetes” a nuestra aplicación ciégamente. Por ejemplo:

1 var util = require('util');

Pero, ¿qué pasa si queremos crear un archivo que queramos requerir? En este capítulo vamos a ver
como hacerlo. Separar el código es buena idea y es algo que trataremos en el próximo capítulo con
todo detalle.
Node.js implementa el estándar que creó CommonJS para cargar módulos, esto facilita mucho la
tarea de exportar e importar archivos en Node.js. Como no pretendo que nadie que lea este libro
conozca el estándar de CommonJS, vamos a explicarlo brevemente.

Exportando con el objeto exports


Todos los scripts de Node exponen un objeto exports de manera que al requerirlos, tenemos lo que
haya en ese objeto. Veamos un breve ejemplo con comentarios de lo que va ocurriendo internamente:

tweets.js

1 // var exports = {};


2
3 var misTweets = ['Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4'],
4 misOtrosTweets = ['Me encanta escuchar Justin Beaver', 'Me gusta Crepúscu\
5 lo'];
6
7 exports.tweets = misTweets;
8 exports.cuentaTweets = function() {
9 return misTweets.length + misOtrosTweets.length;
10 };
11
12 // exports = {
13 // tweets : misTweets,
14 // cuentaTweets : function() { ... }
15 // };
Exportando en Node.js 43

En los comentarios puedes ir viendo cómo se va comportando la variable exports. Podemos


añadir tantas propiedades y funciones como queramos. Estas funciones tendrán acceso al resto del
contenido del archivo pero no podremos acceder a nada que no haya sido exportado. En nuestro caso,
queremos mantener en secreto nuestros Tweets sobre nuestros gustos… No porque sean escandalosos
(¿lo son?), si no porque a nadie le interesan. No obstante, los añadimos a la cuenta de Tweets
Veamos cómo importarlo:

app.js

1 var tweets = require('./tweets');


2
3 console.log(tweets.tweets);
4 // [ 'Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4' ]
5
6 console.log(tweets.cuentaTweets());
7 // 4
8
9 console.log(tweets.misOtrosTweets);
10 // undefined

Lo primero que observamos es que en el require tenemos que poner ./ antes del nombre del archivo,
indicando que el archivo está en el mismo directorio en el que está app.js. Si lo hubiéramos guardado
en la carpeta aplicaciones, tendríamos que haber puesto algo como aplicaciones/tweets. La
extensión podemos omitirla si queremos ya que Node.js se encargará de encontrarla por su cuenta.
Como ves, ahora la variable tweets contiene el valor de exports del contenido del archivo
tweets.js y, como podrás comprobar, no tenemos acceso al valor de misOtrosTweets pero la función
cuentaTweets ha accedido a su longitud correctamente.
Cabe destacar que el llamar tweets a la variable es solo una convención de llamar a la variable igual
que al fichero que se importa, podríamos haber hecho esto perfectamente:

1 var noSonTweets = require('./tweets');


2 console.log(noSonTweets.tweets);
3 // [ 'Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4' ]

Exportando con module.exports


¿Qué es esto? ¿No es lo mismo? La verdad es que no. Son muy parecidos. exports es realmente un
pequeño ayudante de module.exports y es lo que realmente es devuelto a la hora de importarlo,
no exports. exports lo que hace es recoger todas las propiedades y funciones y añadirlas a
Exportando en Node.js 44

module.exports si éste último no tiene ya algo. Si hemos añadido algo a module.exports los
exports serán obviados.

Entonces, si en el ejemplo anterior ponemos esto:

tweets.js

1 module.exports = 'MIS TWEETS';


2 exports.cuentaTweets = = function() {
3 return 4;
4 };

Y ahora hacemos esto:

app.js

1 var tweets = require('./tweets');


2
3 console.log(tweets.cuentaTweets);
4 // TypeError: Object MIS TWEETS has no method 'cuentaTweets'

Como ves, nuestra inútil aplicación ha obviado por completo la función cuentaTweets ya que
module.exports había tenido lugar.

module.exports puede tomar el valor de cualquier cosa de JavaScript: ya sea una función, una matriz
o una “clase”:

tweets.js

1 function Tweets (cuenta) {


2 this.cuenta = cuenta;
3 }
4
5 Tweets.prototype.leerTweets = function() {
6 console.log('Leyendo tweets de "%s"',this.cuenta);
7 }
8
9 module.exports = Tweets;
Exportando en Node.js 45

app.js

1 var Tweets = require('./tweets');


2
3 var belelros = new Tweets('@Belelros'),
4 funcion13 = new Tweets('@Funcion13');
5
6 belelros.leerTweets(); // Leyendo tweets de "@Belelros"
7 funcion13.leerTweets(); // Leyendo tweets de "@Funcion13"

Como ves, hemos separado el código de la clase a otro archivo y ahora podemos crear objetos de ese
tipo fácilmente, cada uno con su propio valor.

Algunas aclaraciones
Cabe destacar varias cosas para evitar confusiones:

• Las llamadas a exports o module.exports han de ser inmediatas en el código, no pueden estar
dentro de un callback ni nada parecido. Esto no funcionará por ejemplo:

1 setTimeout(function() {
2 module.exports = 'OLA K ASE';
3 }, 100);

• Asociar propiedades y métodos a module.exports es exactamente lo mismo que hacerlo a


exports. Por ejemplo esto:

1 module.exports.saludar = function(nombre) {
2 console.log('¡Hola %s!',nombre);
3 };

Es idéntico a esto:
Exportando en Node.js 46

1 exports.saludar = function(nombre) {
2 console.log('¡Hola %s!',nombre);
3 };

• module es la variable global dentro de un archivo. Funciona de la misma manera que window
en el navegador.

Eligiendo el método adecuado


En este momento estarás teniendo un cacao mental y estarás pensando ¿y cuándo elijo uno u otro?
Yo he estado ahí. La verdad es que es cuestión de preferencias porque realmente, module.exports
es exactamente lo mismo que exports. No obstante, para evitar confusiones, lo mejor es quedarse
con module.exports.
Las reglas son las siguientes:

• Si vamos a crear una clase y queremos exportarla, lo ideal es definir la clase, sus prototipos y
luego asociarla directamente a module.exports.

tweets.js

1 function Tweets (cuenta) {


2 this.cuenta = cuenta;
3 }
4
5 Tweets.prototype.leerTweets = function() {
6 console.log('Leyendo tweets de "%s"',this.cuenta);
7 }
8
9 module.exports = Tweets;

• Si vamos a crear un objeto con propiedades, como en un fichero de configuración, haremos


algo así:
Exportando en Node.js 47

config.js

1 module.exports = {
2 usuario : 'root',
3 clave : 'root',
4 servidor : 'localhost'
5 };

Pasando parámetros a require


A veces puede que quieras personalizar lo que obtienes de un require pasando un parámetro. Esto
puede ayudarte enormemente a reducir la cantidad de código que necesitas escribir. Pongamos
el ejemplo de que necesitas conectar a dos bases de datos y tienes un módulo que se encarga de
devolverte algo para que puedas conectar:

bdatos.js

1 var config = {
2 usuario : 'root',
3 pass : 'root',
4 servidor : 'localhost'
5 };
6
7 module.exports = function(opciones) {
8 var usuario = opciones.usuario || config.usuario,
9 pass = opciones.pass || config.pass,
10 servidor = opciones.servidor || config.servidor;
11
12 return {
13 consulta : function() {
14 console.log('Haciendo consulta al servidor %s con el usuario %s', ser\
15 vidor, usuario);
16 }
17 }
18 };
Exportando en Node.js 48

app.js

1 var local = require('./bdatos')({ 'usuario' : 'Trololo' }),


2 remoto = require('./bdatos')({ 'servidor' : 'remoto' })
3
4 local.consulta();
5 // Haciendo consulta al servidor localhost con el usuario Trololo
6 remoto.consulta();
7 // Haciendo consulta al servidor remoto con el usuario root

En bdatos creamos un objeto con las variables de conexión por defecto y luego exportamos la
función. La función es una de esas joyas de JavaScript: una función que devuelve funciones ¡dawg²⁶!
Básicamente estamos protegiendo el valor de las variables y asegurándonos de que si no pasamos
alguno de los parámetros, tengamos un valor por defecto. Esta función a su vez devuelve un objeto
(que es lo que se queda al final), con una función consulta que se encarga de realizar la supuesta
consulta a la base de datos.
En app.js requerimos 2 veces el mismo archivo, pero pasando diferentes opciones para obtener
diferentes resultados.
Como ves, es realmente sencillo exportar código que pueda ser reusado por otras partes de la
aplicación, lo cual ayuda enormemente a que el código sea más sencillo de mantener.
²⁶http://i.imgur.com/TWmBWl8.jpg
Organizando el código de nuestra
aplicación
La forma en que organicemos el código de nuestra aplicación es bastante subjetiva y suele ser algo de
preferencia personal. No obstante recuerdo que, cuando comencé con Node.js, todo el código estaba
en app.js y en principio todo parecía funcionar. Pasa el tiempo y la aplicación crece y quieres reusar
código o cambiar algo y poco a poco tu dedo del scroll del ratón empieza a quejarse… ¡estás haciendo
mucho scroll por el código!
Es en esos momentos cuando uno decide que hay que comenzar a separar las cosas en archivos y
poner cada una su lugar. Como digo, es algo de preferencia personal y ningún módulo de Node.js te
fuerza a usar ninguna estructura en particular. No hay ninguna buena estructura reconocida aunque
con un poco de buenas prácticas y experimentación, te harás con una estructura que se ajuste a tus
necesidades. Sin embargo, en este libro voy a plasmar una estructura que yo considero buena.
La idea principal que has de tener en mente conforme escribes el código es, ¿cómo podría separar
esto? ¿es esto un módulo con el que usaría require para traer esa funcionalidad?
Si la respuesta a esa pregunta es sí, deberías buscar primero si ya hay alguna librería en el mercado,
que te pueda servir. En caso de que no, porque sea código particular a tu aplicación, tendrás que
escribir la tuya propia y más adelante, si lo crees necesario, podrías publicarla o bien mantenerla
para ti.
¡Pero basta de literatura!
En la siguiente imagen puedes ver una imagen de una estructura que yo considero adecuada para
comenzar a organizar nuestra aplicación.

Figura 3: Estructura de carpetas

Como ves es una estructura muy sencilla aunque cuando veamos Express se complicará un poco.
Veamos las carpetas una a una.

• models : En esta carpeta guardaremos los modelos de nuestra aplicación, por ejemplo Tweet o
Usuario.
• lib : En esta carpeta nos encargaremos de guardar todas las librerías que creemos. Y por
librerías nos referimos a funcionalidades de la aplicación en concreto.
Organizando el código de nuestra aplicación 50

– db : Aquí guardaremos el código relacionado con la conexión a la base de datos, sea esta
MongoDB o MySQL o lo que sea.
• config.js : Aquí guardaremos la configuración de la aplicación, normalmente en un objeto
sencillo y, habitualmente, diferenciando si la configuración es en producción o en desarrollo.
• app.js : Este archivo es el encargado de que todas las piezas funcionen, de hacer de pegamento
e iniciar todos los procesos necesarios.

Vamos a hacer un ejercicio de imaginación para poner esto un poco más en contexto. Imaginemos
que nuestra aplicación es un panel para mostrar tickets de soporte de nuestra empresa y vamos a
mostrar correos y tweets. A grosso modo, tendremos un modelo llamado Correo y otro que se llame
Tweet. Igualmente, tendremos una librería que se encargue de leer tweets y otra de leer correos por
lo que tendremos un LectorCorreo y otra que sea LectorTwitter. Como son bastante complejas, las
metemos en sus respectivas carpetas para añadir más módulos a esa librería como: TweetParser o
EmailParser.

El archivo de configuración
Veamos un breve ejemplo de cómo sería un archivo config.js:

config.js

1 module.exports = {
2 'production' : {
3 'bdatos' : 'miaplicacion',
4 'usuario' : 'root',
5 'password' : 'lT10Vw8H',
6 'debugging' : false
7 },
8 'development' : {
9 'bdatos' : 'test',
10 'usuario' : 'root',
11 'password' : '',
12 'debugging' : true
13 }
14 };
Organizando el código de nuestra aplicación 51

app.js

1 var config = require('./config');


2 config = config[process.env.NODE_ENV] || config['development'];
3
4 console.log(config);

Si ejecutamos NODE_ENV=production node app.js veremos que aparece la configuración que hemos
puesto en el apartado production y, si ponemos development o cualquier cosa que no tengamos
contemplada, veremos la opción de development.

Recuerda
Las variables de entornos y NODE_ENV en concreto fueron tratadas en el capítulo anterior.
Quizá quieras echar nuevamente un vistazo a esa sección.

Para compartir los datos de la configuración con las diferentes partes de la aplicación que la necesitan
tenemos varias formas:

Usando una variable global


Si has leído algún buen artículo sobre JavaScript en el pasado, sabrás que no es un buen patrón de
desarrollo a seguir. No obstante, no voy a dejar de explicarlo.
En Node.js, el objeto global se llama… global o GLOBAL y lo podríamos considerar el objeto window
de Node.js. Así que podríamos hacer algo así:

1 global.config = require('config');

Y podríamos acceder a su valor desde cualquier punto de la aplicación. No obstante, ¡no sigas este
camino!

Requiriendo el archivo
Así es, podemos simplemente volver a requerir el archivo y lo tendremos disponible dentro de
cualquier módulo que queramos. De esta forma estaremos haciendo algo más correcto.
Organizando el código de nuestra aplicación 52

Pasando parámetros
A mi modo de ver, no tiene mucho sentido que, si creas un módulo para leer correos, el módulo
sea capaz por sí mismo de decidir a qué cuenta de correo ha de conectarse leyendo un archivo
de configuración. Esto empeora notablemente la capacidad que tiene tu código de ser reutilizable
(porque necesitarías un archivo con unos datos concretos).
Por ello, lo mejor es pasar los datos necesarios o bien al requerir el módulo (como ya hemos visto)
o bien al crear una instancia de la clase que sea.
Emisión de eventos con
EventEmitter
Muchas de las clases de Node.js emiten eventos. Este es sin duda uno de los motivos por los que
Node.js es tan rápido, ya que en vez de leer archivos o bases de datos con cada petición, una vez
que comienza el servidor, se declaran las funciones y las variables y todo queda a la espera de que
ocurra un evento.
Si estás acostumbrado a usar jQuery, este tipo de comportamiento no te será del todo desconocido,
ya que es muy habitual asociar funciones a eventos que ocurren en el DOM, como un click de un
botón, cuando pulsamos una tecla, etc.
Los eventos forman parte del núcleo de Node.js y vamos a intentar ver cómo los interpreta Node.
Antes, me gustaría hablar un poco sobre el patrón del Observador, sobre el cual se basan los eventos.

Patrón del observador


Este patrón es un patrón de diseño, en el cual un objeto (sujeto), tiene una serie de observadores. El
objeto lanzará un aviso de que algo ha cambiado, o de que está haciendo algo. Todos los observadores
de ese sujeto, reaccionarán de alguna manera ante ese anuncio.
Voy a poner un ejemplo que creo que todo el mundo podrá entender. Imagina que viajas todas las
mañanas en metro para ir al trabajo, quizá no tengas que imaginarlo porque sea así… La compañía
de metro ofrece alertas de las líneas por si surge algún problema a través de Twitter. Ese sería nuestro
sujeto y nosotros, que seguimos la cuenta de Twitter, los observadores.
De repente, recibimos una alerta de que la línea que usamos normalmente está cerrada porque un
tren quedó averiado en medio de una estación. Ha ocurrido un evento. Ahora tendremos que re-
adaptar nuestro recorrido o llamar a nuestro jefe/a para decir que no podremos ir a trabajar.

Emitiendo eventos con Node.js


Veamos un breve ejemplo:
Emisión de eventos con EventEmitter 54

1 var events = require('events');


2 var canalDeTwitter = new events.EventEmitter();
3
4 canalDeTwitter.on('retraso',function(){
5 console.log('Avisando al jefe…');
6 });
7
8 canalDeTwitter.emit('retraso');

Si ejecutas eso verás que, automáticamente, aparece la cadena Avisando al jefe…. En nuestro caso,
canalDeTwitter es nuestro sujeto y la función que asociamos al evento retraso es un observador.
El método on que nos ofrece la clase EventEmitter nos permite estar pendiente de los eventos. El
primer parámetro es el nombre del evento y segundo es la función que se ejecuta cuando el evento
ocurre.
Quizá ahora estés pensando, vaya, podría haber avisado al jefe con una función nada más y eso sería
todo. Lo interesante es que podemos hacer también algo como esto:

1 canalDeTwitter.on('retraso', avisarAlJefe);
2 canalDeTwitter.on('retraso', avisarAPareja);
3 canalDeTwitter.on('retraso', guardarEnBD);

De manera que todo eso podría estar distribuido por cualquier punto de la aplicación y todos se
ejecutarían cuando el evento ocurriera. ¿A la vez? No. Ya hemos dicho que las cosas no se ejecutan
a la vez, se ejecutan en el orden en que quedaron asociadas al emisor de eventos.

Pasando parámetros a los eventos


Vale, hasta ahora solo sabemos que ha habido un retraso pero… ¿qué lo ha causado? ¿Cómo puedo
recibir un mensaje? Es muy sencillo:

1 canalDeTwitter.on('retraso',function(mensaje){
2 console.log('Avisando al jefe del aviso ""%s" enviado por "%s"', mensaje.\
3 mensaje, mensaje.autor);
4 });
5
6 canalDeTwitter.emit('retraso', {
7 'mensaje' : 'Un tren se ha estropeado',
8 'autor' : 'La compañía de metro'
9 });

Deberías recibir este mensaje: Avisando al jefe del aviso ""Un tren se ha estropeado"
enviado por "La compañía de metro". Como puedes ver, es realmente potente y permite a varias
partes de tu aplicación estar atentas para reaccionar ante los eventos.
Emisión de eventos con EventEmitter 55

Dejando de escuchar eventos


En alguna ocasión puede resultarte interesante el dejar de escuchar un evento. Este tipo de
comportamiento es habitual implementarlo cuando se está borrando el objeto (por el motivo que
sea), para liberar recursos.
La sintaxis es muy sencilla:

1 canalDeTwitter.removeListener('retraso', nombreFuncion);

O podemos eliminarlos todos a la vez:

1 canalDeTwitter.removeAllListeners('retraso');

Refactorizando el ¡Hola mundo!


Ahora que ya sabemos un poco más sobre los eventos, podemos retomar el código original que
escribimos al inicio del libro:

Hola mundo en el servidor

1 var http = require('http');


2
3 var server = http.createServer(function (request, response) {
4 response.writeHead(200, {"Content-Type": "text/plain"});
5 response.end("¡Hola Mundo!\n");
6 }).listen(8080);
7
8 console.log('Servidor escuchando por el puerto 8080');

Esto es exactamente lo mismo que poner:


Emisión de eventos con EventEmitter 56

Hola mundo en el servidor con eventos

1 var http = require('http');


2
3 var server = http.createServer().listen(8080);
4
5 server.on('request',function (request, response) {
6 response.writeHead(200, {"Content-Type": "text/plain"});
7 response.end("¡Hola Mundo!\n");
8 });
9
10 console.log('Servidor escuchando por el puerto 8080');

El servidor, cuando recibe una petición emite un evento request y nos pasa los parámetros request
(que es la petición) y response (que es la respuesta que podemos devolver).

Creando clases que emiten eventos


Lo habitual no es lo que hemos hecho ahora, pero como introducción está bien. Si quieres emitir
eventos desde tu clase, lo habitual es que esa clase extienda a la clase EventEmitter. De esta forma,
la clase en sí se convierte en sujeto. Tenemos dos formas de hacerlo, a la JavaScript tradicional o a
la Node.js.
El esqueleto será común para todos y, símplemente, tendremos que hacer funcionar el siguiente
código:

1 var events = require('events');


2
3 function ClienteTwitter (canal) {
4 this.canal = canal;
5 }
6
7 ClienteTwitter.prototype.obtenerTweets = function() {
8 this.emit('retraso', 'Ha ocurrido un retraso, anunciado en el canal "' + \
9 this.canal + '"');
10 }
11
12 // Aquí colocaremos el código que veremos más abajo
13
14 var metroMadrid = new ClienteTwitter('@metro_madrid'),
Emisión de eventos con EventEmitter 57

15 autobusesMadrid = new ClienteTwitter('@emtmadrid');


16
17 function registrarMensaje(mensaje) {
18 console.log(mensaje);
19 }
20
21 metroMadrid.on('retraso', registrarMensaje);
22 autobusesMadrid.on('retraso', registrarMensaje);
23
24 metroMadrid.obtenerTweets(); // Ha ocurrido un retraso, anunciado en el can\
25 al...

JavaScript tradicional
La forma tradicional es usando la propiedad prototype que tienen todos los objetos de JavaScript.
Esta propiedad nos data otra a su vez, __proto__ que indica la súper-clase. Podríamos hacer algo
así:

1 ClienteTwitter.prototype.__proto__ = events.EventEmitter.prototype;

De esta forma, estamos indicando que la super clase de ClienteTwitter es el prototipo de


EventEmitter.

Con Node.js
La herencia de clases con Node.js es más sencilla pero requiere que usemos otro módulo, además de
events y que cambiemos un poco la definición. No obstante, el resultado es bastante más natural:

1 var util = require("util");


2
3 function ClienteTwitter (canal) {
4 this.canal = canal;
5 events.EventEmitter.call(this);
6 }
7
8 util.inherits(ClienteTwitter, events.EventEmitter);

En esta ocasión, si sabemos algo de inglés, la frase toma sentido ya que estamos diciendo que la
primera clase hereda de la segunda.
Emisión de eventos con EventEmitter 58

Un ejemplo real
Vale, hasta ahora lo que hemos hecho ha sido lanzar un evento rápidamente y ver la respuesta
rápidamente. Vamos a hacer un pequeño ejercicio para ir abriendo boca.
Lo que vamos a hacer es una petición a una página web real: http://isitchristmas.com/. Esta
página nos dice si es Navidad y, en el momento en que estoy escribiendo esto, no lo es. He elegido
esta página porque no tiene mucho misterio en el código interno así que no veremos mucha basura
al obtenerla:

1 var http = require('http');


2
3 var opciones = {
4 hostname : 'isitchristmas.com',
5 port : 80,
6 method : 'GET'
7 }
8
9 var peticion = http.request(opciones);
10
11 peticion.on('response', function(respuesta) {
12 respuesta('data', function (trozo) {
13 console.log('BODY: ' + trozo);
14 });
15 });
16 peticion.on('error', function(e) {
17 console.log(e.message);
18 });
19
20 peticion.end();

Si ejecutas esto en Node, verás que en pantalla aparece lo mismo que si abres la página y revisas el
código de la misma. Pero… ¿qué es lo que está ocurriendo?
Lo que hacemos es crear una peticion a la que le pasamos una serie de parámetros. En nuestro
caso la página que queremos obtener, el puerto (80 por defecto) y el método, que en este caso es GET.
peticion es un emisor de eventos y en este caso estamos atentos a dos eventos:

1. response es cuando obtenemos una respuesta por parte del servidor y nos pasa un parámetro
con la respuesta en sí, que es a su vez otro emisor de eventos. Node hace esto así para que la
lectura del cuerpo sea asíncrona y la vaya parseando por trozos. Cada vez que un trozo está
disponible, emite un evento data que aprovechamos para pasarlo a la consola.
Emisión de eventos con EventEmitter 59

2. error es cuando… ¡ocurre un error! Estoy seguro de que no lo adivinaste. En cualquier caso,
anunciamos el error.

Finalmente, peticion.end() es lo que hace que realmente se ejecute la petición y, eventualmente,


se dispararán los eventos.
Como ves, es realmente sencillo y es algo que está presente en muchos de los módulos de Node.js
Los Streams en Node.js
Ahora que ya hemos visto los eventos en Node.js, estamos preparados para pasar al siguiente nivel,
los Streams. Stream es una palabra inglesa que implica, en informática, flujo de datos. Dado que no
es correcto traducir Stream directamente por flujo de datos, vamos a quedarnos con Stream.
Los Streams son otra de las funcionalidades que están construidas en muchos de los módulos de No-
de.js. Cuando vimos la introducción a Node.js, nos presentaban la herramienta como especialmente
creada para modelos de Entrada/Salida de datos. Esto suele ser la parte más lenta de una aplicación.
Node.js nos facilita un poco la tarea usando abstracción en estos casos así que nos dá igual si estamos
viendo bases de datos, ficheros de texto, etc.

¡Atención!

Los Streams han cambiado mucho en Node.js desde que se saltó de la versión 0.8 a
la 0.10 y ahora son conocidos como streams2. En el libro explicaremos cómo usar las
características implementadas porque, aunque se pueden hacer a la vieja usanza, todo
apunta a que será la forma de funcionar en el futuro.

¿Que es un Stream?
Si sabes algo de Unix, no te será muy difícil comprenderlo puesto que los Streams son, básicamente,
pipes (tuberías o cadenas de procesos). Hay 5 tipos básicos de Streams que puedes crear: Readable
(lectura), Writable (escritura), Duplex (ambos), Transform (transformación de datos) y PassThrough.
Éstos son Emisores de Eventos por lo que podemos asociar funciones a eventos que emitan y emitir
eventos como hemos visto en la sección anterior. Los Streams emiten una serie de eventos “fijos”
y que deben ser implementados. No obstante, nada nos impide crear eventos personalizados que
tengan sentido con el Stream.
Crear un Stream es bastante sencillo:

1 var Stream = require('stream');


2 var stream = new Stream;

Vamos a ver cómo usarlos.


Los Streams en Node.js 61

La función pipe
La función pipe() nos permite mandar lo que pase en el Stream hacia otro sitio y que se encargue
de gestionar sus eventos. Esa función se llama sobre un Stream de lectura pasando como parámetro
uno de escritura y nos devuelve este último. Para graficarlo:

1 origen.pipe(destino);

Si destino resulta que también es de lectura, podríamos volver a encadenar:

1 origen.pipe(destino).pipe(destinoFinal);

Además, pipe se encarga de gestionar la memoria por nosotros. Es decir, si por lo que sea se llena
la memoria de escritura (el buffer), pipe se encargará de mantener a la espera los datos leídos hasta
que el buffer se vacíe… ¡Y todo esto sin hacer nada!

Lectura - Readable
Los Streams Readable emiten eventos data cada vez que obtienen un trozo de lo que sea que estén
leyendo. Si has leído hace poco el capítulo sobre los eventos, el objeto respuesta que obtenemos al
hacer una petición http, es un Stream. No obstante, esto se hace de modo interno, sin que tengamos
que ocuparnos de emitir eventos por nuestra cuenta.

Figura 4: Eventos del Stream de Lectura

Para hacer que tu clase sea un stream de Lectura, ha de heredar de la clase stream.Readable
e implementar el método _ read(tamaño). El tamaño es algo “recomendable” y muchas de las
implementaciones pueden ignorarlo directamente. Cuando el Stream se hace con datos, los envía
llamando a this.push(trozo). Puede que te estés preguntando… ¿a dónde los envía? Pues, al otro
lado de la tubería. La tarea del Stream es solo una, en este caso la de leer datos, si al otro lado de
la tubería le ponemos un Stream que se encargue de escribir los datos en el disco conforme van
llegando pues eso es lo que hará. Esa es la potencia del Stream ya que abstrae el transporte de datos
desde la lectura hasta la escritura.
Vamos a crear un sencillo Stream que nos facilite la lectura de una matriz.
Los Streams en Node.js 62

1 var util = require('util'),


2 Stream = require('stream');
3
4 function MiStreamDeLectura(array) {
5 var self = this;
6
7 Stream.Readable.call(this, { objectMode: true });
8
9 this._read = function (tamano) {
10 array.forEach(function(elemento){
11 self.push(elemento + '\n');
12 });
13 this.push(null);
14 }
15 }
16
17 util.inherits(MiStreamDeLectura, Stream.Readable);
18 var misTweets = ['Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4'];
19 var stream = new MiStreamDeLectura(misTweets);
20 stream.pipe(process.stdout);

En este ejemplo creamos una clase que recibe como parámetro un array. Primero nos aseguramos que
guardamos el valor de this en la variable self. Esto nos servirá más adelante ya que cambiaremos
el valor de this dentro del forEach.

Sobre esta técnica…


Esta técnica es conocida en JavaScript como closure y es una de las herramientas más
potentes que tiene el lenguaje. Si no estás muy familiarizado con el uso de esta técnica,
te recomiendo que leas este artículo²⁷ que explica cómo funciona el alcance (scope) de
las funciones en JavaScript e introduce la técnica de las closures.

La siguiente línea es en la que llamamos al constructor original de Stream.Readable sobre nuestra


actual clase. Esta es la forma que tenemos en JavaScript de llamar al constructor de la clase que
heredamos, no es que sea muy sexy ya que no es algo como lo que haríamos en PHP:

²⁷http://www.funcion13.com/2012/03/16/comprendiendo-las-variables-objetos-funciones-alcance-y-prototype-en-javascript/
Los Streams en Node.js 63

1 class MiStreamDeLectura extends StreamReadable


2 {
3 function __construct()
4 {
5 parent::__construct();
6 }
7 }

o Java:

1 public class MiStreamDeLectura extends StreamReadable {


2 public MiStreamDeLectura() {
3 super();
4 }
5 }

En JavaScript tenemos que indicar claramente el constructor de qué clase vamos a usar. Con call
pasamos el argumento this que queremos que use que, en este caso es el propio objeto.
Al constructor de Stream.Readable le pasamos una opción objectMode que, si es true, indica que
el Stream debe comportarse como un flujo de objetos y no un buffer. En la práctica, casi siempre
querremos esto ya que indica que stream.read(n) devuelve un único valor y no un Buffer de tamaño
n.

La función _read tan solo se encarga de recorrer la matriz con la función forEach y va añadiendo los
datos al Stream a través de push con un retorno de carro al final para que la salida sea más bonita.
Si os fijáis, hemos ignorado el valor de tamano ya que es una recomendación, no una obligación.
Cuando terminamos el bucle, hacemos push(null). Esto le indica al Stream que hemos llegado al
final y que ya no va a recibir más datos por lo que está listo para cerrarse.
Luego hacemos que la clase que hemos creado, MiStreamDeLectura, herede de Stream.Readable.
Además, creamos una matriz de relleno que nos sirve para ilustrar el ejemplo.
Finalmente, usando la función pipe, redirigimos los datos del Stream hacia process.stdout que no
es más que el terminal y lo cual permite que veamos el resultado por la pantalla.
Si lo ejecutamos, verás que aparecen por orden todos los “tweets” en la pantalla.

Escritura - writable
Los Streams de escritura sirven para… escribir datos.
Para hacer que tu clase sea un stream de Escritura, ha de heredar de la clase stream.Writable
e implementar el método _ write(trozo, codificacion, callback). El trozo, no es más que
Los Streams en Node.js 64

la porción de datos que ha sido leída. La codificacion se usa en el caso de que el trozo sea una
cadena, para codificarla claro… La codificación puede ser utf8, utf16le, ucs2, ascii o hex. Aunque
normalmente no tendremos que tocarla, lo más habitual será utilizar utf8 en caso de que vayamos
a escribir caracteres especiales.

Figura 5: Eventos del Stream de Escritura

El callback es la función que se ejecutará (con un argumento inicial de error) cuando hayamos
terminado de procesar nuestro trozo. Ten en cuenta que esta función no debe ser llamada por el
código directamente y la usará la clase de manera interna.
Y ahora te estarás preguntando… ¿si no puedo llamar yo a _write cómo escribo datos? Con la
función write, que recibe los mismos parámetros que la función _write siendo opcionales los dos
últimos. Esta función nos devuelve true o false. true significa que todo va bien y que podemos
seguir mandando datos todavía. false significa que el buffer está lleno y que por tanto, no puede
transportar más datos. En este caso los datos serán enviados en breve. Un ejemplo muy gráfico y que
quizá te ayude. Imagina el buffer como una furgoneta que has alquilado para hacer una mudanza.
Tu primo, que te está ayudando, te dice: “Aun caben más cajas, sigamos trayendo más” (true). Llega
un punto en que la furgoneta se llena y te avisa: “¡Para de traer cajas que no caben más! ¡Estamos
listos para partir!” (false). El Stream emitirá un evento drain cuando esté listo para volver a recibir
datos.
Los Streams de escritura nos ofrecen también el método end() que simplemente indica que el Stream
ya ha terminado de escribir. Este método acepta los mismos parámetros que si llamáramos a write,
permitiéndonos escribir algo de cierre en el Stream. La llamada a esta función emite un evento
finish que nos permite saber que el Stream ha terminado su trabajo.

Vamos a ver cómo podemos hacer un sencillo Stream de escritura para nuestro Stream de lectura
anterior.
Los Streams en Node.js 65

1 function MiStreamDeEscritura() {
2 this.valor = "";
3
4 Stream.Writable.call(this);
5
6 this._write = function (trozo, codificacion, callback) {
7 this.valor += trozo.toString();
8 callback();
9 };
10 }
11
12 util.inherits(MiStreamDeEscritura, Stream.Writable);
13
14 var misTweets = ['Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4'];
15 var escritura = new MiStreamDeEscritura();
16 escritura.on('finish',function(){
17 console.log(this.valor);
18 });
19
20 var lectura = new MiStreamDeLectura(misTweets);
21 lectura.pipe(escritura);

Antes de nada, decir que la implementación de este Stream es bastante trivial ya que únicamente
guardamos el valor en memoria, pero podría guardarlo en disco o en una base de datos, o en cualquier
otro sitio.
En el constructor de nuestra clase, inicializamos el valor a una cadena vacía para poder rellenarla
con lo que vayamos recibiendo en nuestro Stream. Esta vez, no le pasamos nada al constructor de
Writable como lo hicimos con Readable.

La implementación de _write no esconde complejidad alguna ya que únicamente vamos encade-


nando el trozo que convertimos a cadena con la función toString a valor. En nuestro caso, como
ya hemos almacenado el dato, llamamos a callback (por si la hubiera), sin pasar ningún error ni
parámetro ya que la implementación es bastante simple.
Después asociamos una función al evento finish para que podamos saber que ha acabado y poner
en consola el contenido de la variable valor.
Si te has fijado, no hemos tenido que llamar a write() ni a end() nosotros si no que, al ser Streams,
pipe() sabe lo que tiene que hacer conforme va transmitiendo datos sin que tengamos que hacer
nada de particular.
Los Streams en Node.js 66

Lectura y Escritura - Duplex


Los Streams Duplex son de escritura y de lectura a la vez permiten fuentes de datos que transmiten
y reciben datos.
Para hacer que tu clase sea un Stream Duplex, ha de heredar de la clase stream.Duplex e implementar
los métodos _ write(trozo, codificacion, callback) y _ read(tamaño) que ya hemos visto.

Transformación - Transform
Este tipo de Streams nos sirven para transformar datos: Entran datos y salen datos ligeramente
transformados. Se considera buena práctica el no transformar los datos en ninguno de los Streams
anteriormente explicados y hacerlos en uno de estos. Supongamos que queremos transformar un
XML a JSON y guardarlo, lo mejor sería tener 3 Streams:

1. El primero de lectura, en el que entran los datos


2. El segundo, de transformación, que se encarga de transformar cada nodo, en un objeto JSON
3. El último, de escritura, encargado de guardar los datos

Para hacer que tu clase sea un Stream Transform, ha de heredar de la clase stream.Transform
e implementar el método _transform(trozo, codificacion, callback) que, como ves, es
esencialmente el método _write de los Streams Writable.
Vamos a ver cómo implementamos este tipo de Stream para hacer que al pasarle una matriz de
números, nos devuelva la enésima potencia del número:

1 function MiStreamDeTransformacion(potencia) {
2 Stream.Transform.call(this, { objectMode: true });
3
4 this._transform = function (trozo, codificacion, callback) {
5 var numero = parseInt(trozo,10);
6 this.push(Math.pow(numero,potencia).toString() + '\n');
7 callback();
8 };
9 }
10
11 util.inherits(MiStreamDeTransformacion, Stream.Transform);
12
13 var numeros = [1,2,3,4];
14 var escritura = new MiStreamDeEscritura();
15 var cuadrado = new MiStreamDeTransformacion(2);
Los Streams en Node.js 67

16 escritura.on('finish',function(){
17 console.log(this.valor);
18 });
19
20 var lectura = new MiStreamDeLectura(numeros);
21 lectura.pipe(cuadrado).pipe(escritura);

La implementación es realmente sencilla.


Lo primero que verás es que vuelve objectMode: true tal y como lo usamos en el Stream Readable.
La función _transform lo primero que hace es convertir el trozo en un entero con la función
parseInt. Luego, calcula la potencia y la vuelve a convertir a cadena almacenándola valiéndonos
de push. Esto es porque los Streams solo manejan cadenas de caracteres ( o buffers ).
Si ejecutas este script, verás que en pantalla aparecen los cuadrados de los valores almacenados en
numeros.

Pasarela - PassThrough
El Stream del tipo PassThrough es una subclase de Transform y… a ver como lo digo… ¡no hace nada!
Tan solo pasa los datos de la entrada a la salida.
Es habitual implementar este tipo de Streams como una pasarela para “espiar” los datos del Stream,
especialmente para hacer pruebas.
El sistema de archivos
La lectura y escritura de datos siempre es una fuente de problemas en los programas. Las operaciones
contra el disco duro son realmente lentas lo cual suele provocar que el programa no haga nada
durante ese período de tiempo.
La principal idea de Node.js fue la de eliminar esa barrera y hacer que la lectura y escritura de datos
en el disco no fuera tan dramático al convertirlas en tareas asíncronas.

Recuerda
Si quieres saber más sobre la asincronía en Node.js recuerda visitar el primer Capítulo.

Las funciones que vamos a explicar aquí, tienen una versión síncrona que no vamos a ver. Usar este
tipo de funciones va contra la naturaleza de Node.js pero… ¡ahí están! Tan solo hay que añadir Sync
al nombre de la función para que esta sea síncrona.
Las operaciones que vamos a ver son las de lectura y escritura principalmente pero hay muchas más
funciones que te pueden ser de utilidad como funciones para vigilar cambios en un fichero/directorio,
cambiar permisos, etc. Si quieres ver todas las funciones, te recomiendo que eches un vistazo a la
documentación²⁸.

Leyendo ficheros
Antes de seguir, y si estás siguiendo los ejemplos junto a tu ordenador (cosa que te recomiendo),
necesito que crees un fichero con extensión txt en la raíz de tu aplicación, y que le metas cualquier
contenido dentro. Para ilustrar los ejemplos mi archivo se llamará prueba.txt y el contenido:

prueba.txt

1 Lisa necesita un aparato... ¡Seguro dental!

Ahora que ya tenemos nuestro fichero, vamos a leerlo con Node.js:

²⁸http://nodejs.org/api/fs.html
El sistema de archivos 69

1 var fs = require('fs');
2
3 fs.readFile('prueba.txt', function(err,data){
4 if (err){
5 console.error(err);
6 }
7 else {
8 console.log(data);
9 }
10 });

La función recibe la ruta/nombre del archivo, un objeto (opcional) de configuración, y una función
que se llamará cuando haya leído el archivo.
Si estáis siguiendo el ejemplo, aparecerá algo así:

1 <Buffer 4c 69 73 61 20 6e 65 63 65 73 69 74 61 20 75 6e 20 61 70 61 72 61 7\
2 4 6f
3 2e 2e 2e 20 a1 53 65 67 75 72 6f 20 64 65 6e 74 61 6c 21>

¿¡Pero qué!? ¡Tranquilo! Al contrario de lo que pasa con los Streams, tenemos que especificar un
parámetro de encoding para que nos lo muestre correctamente:

1 var fs = require('fs');
2
3 fs.readFile('prueba.txt', {encoding:'utf8'}, function(err,data){
4 if (err){
5 console.error(err);
6 }
7 else {
8 console.log(data);
9 }
10 });

Ahora sí que podremos ver el contenido de nuestro archivo sin problema ninguno.

Escribiendo en ficheros
Escribir en ficheros es igualmente sencillo que leerlos.
El sistema de archivos 70

1 var fs = require('fs');
2
3 fs.writeFile('homer.txt', 'Seguro dental', function(err){
4 if (err){
5 console.error(err);
6 }
7 else {
8 console.log(data);
9 }
10 });

Como ves, la función recibe el nombre/ruta del archivo, los datos a escribir, un objeto opcional de
configuración, y una función que ejecutará cuando haya escrito. Esta vez, Node.js se encarga de
establecer la codificación a utf8 por defecto. ¿No podría haberlo hecho antes?
Si ejecutas la aplicación y abres la carpeta, verás que se ha creado un archivo homer.txt y que dentro
tiene el contenido que le hemos pasado.
Esta función “reemplaza” el contenido del archivo por el que le hayamos pasado. Si lo que queremos
es añadir al archivo, tenemos la función fs.appendFile que recibe los mismos parámetros pero
añade al contenido al final en vez de sobrescribir.

Los Streams y los ficheros


Cuando hemos hablado de los Streams, hemos comentado que los Streams están bien dentro de
Node.js. Para demostrarlo, vamos a ver cómo podemos transvasar un fichero a otro.
Primero vamos a crear un Stream de lectura que apunte a ese fichero:

1 var lectura = fs.createReadStream('prueba.txt');

Esta función acepta un segundo parámetro que es un objeto de configuración al que podemos
cambiarle la codificación (entre otras cosas) y especificar los bytes de inicio y fin con los parámetros
end y start. Estos dos últimos parámetros pueden ser interesantes ya que algunos archivos (como
los de vídeo), almacenan información en los últimos caracteres del archivo.
El parámetro encoding no lo vamos a tocar en esta ocasión (ahora veremos por qué).
Y bien, ahora tenemos una manguera que está empezando a bombear agua… ¿dónde la enchufamos?
¡Pues a la boca de riego!
El sistema de archivos 71

1 var fs = require('fs');
2
3 var lectura = fs.createReadStream('prueba.txt');
4 var escritura = fs.createWriteStream('homer.txt');
5
6 lectura.pipe(escritura);

Como ves, hemos creado un Stream de Escritura con createWriteStream que recibe las mismas
opciones que su análogo, y lo hemos enchufado al Stream de Lectura. Dado que ambos Streams
tratan con buffers, no necesitamos codificar la información ya que va directamente a un archivo
copiando los datos de origen al destino.
Si abres ahora el archivo homer.txt, verás que el contenido es completamente idéntico al que hay
en prueba.txt. ¿¡A qué es fácil?!
Resumen
En este, corto pero intenso, capítulo hemos aprendido algunos de los conceptos fundamentales de
Node.js que nos servirán en nuestro día a día en el desarrollo de nuestras aplicaciones.
El primer tema tratado ha sido la gestión de dependencias con Node.js a través del archivo
package.json. Hemos aprovechado para ver cómo funciona el versionado semántico (y como éste
es importante en Node.js) y cómo instalar dependencias para desarrollo.
Luego, hemos visto la exportación de archivos en Node.js y las dos opciones que tenemos para
hacerlo (aunque en el fondo sean lo mismo) : exports y module.exports. Hemos tratado de explicar
las diferencias entre ambos métodos, las particularidades que pueden tener cada una de ellas así
como elegir el método adecuado para cada ocasión. Finalmente, hemos visto cómo podemos pasar
argumentos a la hora de importar algo que estamos exportando.
A continuación, hemos visto cómo podemos organizar nuestra aplicación Node.js. Teniendo en
cuenta que es siempre algo subjetivo, he intentado ofrecer una visión que pudiera servir y fuera
lógica para la mayoría de los programadores.
Además, hemos tratado la emisión de eventos con EventEmitter, hemos comprendido (esperemos)
el patrón del Observador y cómo éste es implementado por Node.js. Además, hemos creado nuestros
propios eventos y pasado argumentos a éstos para dotarlos de más contexto. Luego, nos hemos puesto
manos a la obra y descubierto cómo podemos crear nuestra propia clase emisora de eventos para lo
cual hemos indagado un poco en la herencia de clases en JavaScript y cómo lograrla más fácilmente
con Node.js. Finalmente, hemos creado una pequeña aplicación que nos descarga una página web y
va emitiendo datos de ésta, aplicando los conocimientos adquiridos.
En siguiente lugar, nos hemos encargado de los Streams, centrándonos en la especificación conocida
como streams2. Hemos intentado descifrar qué es exactamente un script y cómo funciona la función
pipe para encadenar el resultado de un Stream a otro. Nos hemos sumergido en cada uno de los 5
tipos de Streams: Readable, Writable, Duplex, Transform y PassThrough usando ejemplos cuando
nos ha sido posible.
Por último, hemos tratado el tema del sistema de ficheros. Hemos aprendido cómo podemos leer
ficheros y escribirlos. Además, hemos visto cómo podemos aplicar lo aprendido con los Streams al
sistema de ficheros.
En el siguiente capítulo comenzaremos ya a trabajar con Express, descubriendo paso a paso cómo
podemos crear nuestra aplicación y qué podemos hacer con ella en cada caso.
Introducción a Express

Ahora que ya tenemos unas nociones básicas de cómo funciona Node.js y de para qué sirve, estamos
preparados para dar el siguiente paso en nuestro particular camino de iluminación. Así que, ¡cojamos
el Express-o y sigamos aprendiendo! Vale vale… chiste malo.
Llevamos mencionando a Express todo el libro, en estos momentos debe ser como cuando tu madre
te habla de tus primos los del pueblo, los conoces de oídas pero no los conoces realmente. Vamos a
intentar cambiar eso.
Express es un framework que te ayudar a organizar tu aplicación, siguiendo la arquitectura Modelo-
Vista-Controlador en el servidor. En realidad no te fuerza a gran cosa, pero te ayuda. No hay modelos
ni nada por el estilo. Express se inspiró en Sinatra (un framework de Ruby) así que si, por algún casual
lo habéis usado alguna vez, su uso resultará natural.
La característica más potente de Express (a mi modo de verlo) es la versatilidad que te ofrece a la
hora de gestionar las rutas de nuestra aplicación.
Otros frameworks de Node.js
¿No pensaríais que solo tenemos un Framework en Node.js, no? La verdad es que hay muchos y no
quería desperdiciar la oportunidad de hablar de alguno de ellos. Express es el Framework que suele
dar mayor control pero lo cierto es que muchos de los Frameworks en sí están basados en Express.
No obstante, esto tiene un precio: Tienes que desarrollar tu propio código para ciertas cosas.

Meteor
Web²⁹ - Github³⁰

Es uno de los chicos más famosos del lugar. Lograron una financiación bastante curiosa (9 millones
de dolares) que les permite trabajar en el Framework con asiduidad. Tiene todo lo que necesitas y
funciona realmente bien con aplicaciones en tiempo real gracias a Socket.io. Por si fuera poco, se
integra realmente bien con Mongo.
¿La pega? Aparte del fondo de la web, Meteor está alejado del paradigma de Node.js. No usa NPM,
ni usa las buenas prácticas ni los estándares establecidos por la comunidad, ni tampoco REST. Eso
implica que no puedes usar cualquier módulo que haya en el mercado con Meteor ya que tienes que
instalarlo con su propio sistema de paquetes y módulos.

Derby
Web³¹ - Github³²

Una de las cosas en las que Derby presta más atención es en la de permitirnos usar el mismo código en
el Front-end y en el Back-end. Para ello usa un motor llamado Racer que se encarga de sincronizar los
datos entre navegador, servidor y base de datos. ¡Hasta tiene un resolutor de conflictos! Su mayor
esfuerzo está en eliminar la tarea de tener que hacer funcionar juntos el cliente (navegador) y el
servidor.
Como dependencias tiene:

• Express
²⁹http://meteor.com/
³⁰https://github.com/meteor/meteor
³¹http://derbyjs.com/
³²https://github.com/codeparty/derby
Otros frameworks de Node.js 75

• Socket.io
• Browserify
• Stylus
• Less
• UglifyJS
• MongoDB

¿La pega? Está en estado alfa. Nada recomendable para proyectos que vayas a usar en producción

flatiron
Web³³ - Github³⁴

Flatiron intenta encapsular componentes con características concretas para usar, permitiendo a los
desarrolladores poner o quitar lo que les interesa. Flatiron está además desarrollado por Nodejitsu,
una de las compañías que aportan bastante al mundo de Node.js
¿La pega? Yo no lo he llegado a usar pero parece no contar con muchos seguidores y los componentes
usados son para Flatiron, aunque ya haya algo similar en Node.js

TowerJS
Web³⁵ - Github³⁶

TowerJS está fuertemente basado en Ruby on Rails y, al igual que Derby, intenta trabajar en conjunto
con el cliente y el servidor. Tiene algunas cosas realmente útiles en su interior como Redis, jQuery,
Mocha, un ORM para MongoDB, etc. Además su documentación es bastante buena.
¿La pega? En primer lugar, usa CoffeeScript, con lo que ya añade algo más a la lista de las cosas que
hay que saber (Personalmente no estoy a favor de CoffeScrit) Por otro lado, se basa en el concepto
de convención sobre configuración, dando muchas cosas por sentado que, si las entiendes, te puede
ir bien pero no todo el mundo funciona así y, ciertamente, no es el paradigma que está siguiendo
JavaScript a día de hoy.
³³http://flatironjs.org/
³⁴https://github.com/flatiron/flatiron
³⁵http://towerjs.org/
³⁶https://github.com/viatropos/towerjs.org
Otros frameworks de Node.js 76

¿Por qué Express?


Express es un framework bastante maduro. Están ya por la versión 3.2.0 y han ido mejorando
bastante con el tiempo. Cierto es que hay que realizar más trabajo al inicio, pero yo prefiero ser
consciente de lo que funciona y cómo funciona a tener una especie de vodoo que se encargue de
hacer cosas.
Para mi gusto, lo único a criticar de Express es su documentación: tanto Express como Jade podrían
estar mucho mejor documentados en mi opinión.

Una pequeña aclaración


A lo largo del capítulo voy a intentar explicar las partes más oscuras de la documentación de Express
ofreciendo ejemplos y hablando de ellos y tratar las partes más interesantes. Para todo lo demás,
no está MasterCard, pero tenéis la documentación de Express (que deja mucho que desear en mi
opinión) en esta dirección http://expressjs.com/api.html
Instalaci�n de Express
Instalar Express es realmente sencillo y no difiere en nada de lo que hemos visto hasta ahora. No
obstante, Express nos ayuda bastante si lo instalamos como paquete global y nos permite crear
aplicaciones r�pidamente con algo de c�digo ya dentro.
As� que, vamos a instalarla de forma global

1 npm install -g express


Creando la estructura básica
Lo primero que tenemos que hacer es dirigirnos a un directorio en donde queramos crear nuestra
aplicación y luego escribir lo siguiente:

1 express miaplicacion

Si el directorio ya existe y no está vacío, nos avisará. Obviamente, podemos cambiar miaplicacion
por lo que queramos. Esto crea la estructura básica de carpetas recomendada por Express y con los
siguientes módulos:

• Sistema de plantillas: Jade (lo veremos más adelante)


• Sistema de CSS: CSS plano

No obstante, podemos añadir más cosas añadiendo parámetros al comando:

1 -s Soporte para sesiones


2 -e Soporte para plantillas ejs
3 -J Soporte para plantillas jshtml
4 -H Soporte para plantillas hogan.js
5 -c stylus | less Soporte para hojas de estilo con Stylus o Less
6 -f Fuerza la instalación en un directorio que no esté vacío

Por ahora nos basta con lo que tenemos así que entraremos en el directorio y escribiremos npm
install para instalar las dependencias.
Veamos la estructura que nos crea Express:

Figura 4: Estructura de carpetas de Express


Creando la estructura básica 79

Express se ha encargado de crearnos su propio archivo package.json con las dependencias que
necesita y unas carpetas para sugerirnos cómo organizar la aplicación. Veamos cada una de las
carpetas:

• public : Express diferencia el código que se muestra en el navegador del de la aplicación del
servidor. Todo lo que esté en esta carpeta será accesible por el usuario que visite tu aplicación.
– images, javascripts, stylesheets : Creo que os podéis hacer una idea de lo que se
supone que va aquí. No obstante, me gustaría señalar que yo prefiero renombrarlas a
img, js y css ya que es la convención más habitual hoy en día.
• routes : Aunque entraremos en este tema en breve, cada “url” de nuestra aplicación a la que
podemos acceder es una ruta y hay que definirlas en Express. Para mantenerlas separadas
quedan guardadas en este directorio.
• views : Por defecto Express usa Jade y las plantillas están escritas en este pseudo-lenguaje que
veremos más adelante.
Welcome to Express - Bienvenido a
Express
Ahora hagamos un ejercicio. No abras el archivo app.js todavía. Símplemente entra en la carpeta
desde el terminal y escribe lo siguiente:

1 node app.js

Rápidamente verás aparecer en pantalla:

1 Express server listening on port 3000

Abre tu navegador favorito y abre la dirección http://localhost:3000³⁷. Si todo ha ido bien, deberías
poder leer el mensaje que encabeza la sección:

Welcome to Express

¿Has visto qué fácil? Ya tenemos un servidor de Express corriendo en el puerto 3000 y que nos da la
bienvenida. Ahora sí, te dejo abrir el código de app.js y ver lo que tiene dentro. Pero no hace falta
que te vayas y dejes de leer…

1 var express = require('express')


2 , routes = require('./routes')
3 , user = require('./routes/user')
4 , http = require('http')
5 , path = require('path');
6
7 var app = express();
8
9 // all environments
10 app.set('port', process.env.PORT || 3000);
11 app.set('views', __dirname + '/views');
12 app.set('view engine', 'jade');
13 app.use(express.favicon());
14 app.use(express.logger('dev'));
15 app.use(express.bodyParser());

³⁷http://localhost:3000
Welcome to Express - Bienvenido a Express 81

16 app.use(express.methodOverride());
17 app.use(app.router);
18 app.use(express.static(path.join(__dirname, 'public')));
19
20 // development only
21 if ('development' == app.get('env')) {
22 app.use(express.errorHandler());
23 }
24
25 app.get('/', routes.index);
26 app.get('/users', user.list);
27
28 http.createServer(app).listen(app.get('port'), function(){
29 console.log('Express server listening on port ' + app.get('port'));
30 });

Como siempre, vayamos viendo por partes. Ten en cuenta que vamos a ver todos estos temas que aquí
mencionamos por encima con más detalle a lo largo del capítulo así que no te preocupes demasiado
si no entiendes todo lo que lees.
Lo primero que hacemos es traernos todas las dependencias que vamos a usar: express, las rutas, el
módulo http y el módulo path.
Lo siguiente que hacemos es crear una instancia de Express que guardamos en app.
A continuación, establecemos ciertos parámetros de la configuración de Express. En orden de
aparición:

1. El puerto en el que correrá la aplicación. Si establecemos una variable de entorno PORT con
algún valor lo usará, de lo contrario caerá al 3000.
2. El directorio en el que están las vistas.
3. El motor de vistas que usar, por defecto Jade.
4. Uso del Favicon
5. El nivel del log ha de ser el de desarrollo.
6. Middleware que se encarga de parsear el cuerpo de la petición dando soporte para JSON,
urlencode y multi-parte (para ficheros).
7. methodOverride nos permite simular las peticiones DELETE y PUT.
8. Indicamos que vamos a usar las rutas de la aplicación.
9. Indicamos que todo el contenido de public es servido.

Luego, si estamos en desarrollo, añadimos el gestor de errores de Express.


Le llega el turno a definir rutas, y vaya… vemos dos rutas. Nosotros la que hemos visto es la ruta /.
Welcome to Express - Bienvenido a Express 82

¿Qué pasa si entramos en /users?

Pues que no responde con algo como:

1 respond with a resource

¿Y si entramos en /prueba? Pues que obtenemos un error:

1 Cannot GET /prueba

Finalmente, creamos el servidor al que le pasamos nuestra aplicación para que se encargue de
responder todas las peticiones. Le indicamos que escuche en el puerto que hemos configurado, y
en cuanto comienza mandamos un mensaje a la consola.
Como ves, son solo 30 líneas de código (que ni siquiera hemos tenido que escribir), para crear un
servidor web que responde a nuestras peticiones rápidamente.
Ahora vamos a seguir indagando y viendo para qué sirve cada cosa con más profundidad.
Configuración de la aplicación
Cuando obtenemos la variable de la aplicación de express, a través de la función express(), podemos
pasar a configurarla con profundidad con ciertos métodos que esta expone. Para todos estos ejemplos
de código, supongamos que tenemos este código al principio de cada uno:

1 var express = require('express');


2 var app = express();

Guardando y obteniendo valores en la aplicación


Express nos permite guardar valores dentro de la aplicación. Podemos por ejemplo guardar la versión
que tenemos para mostrarla en algún punto.
Hacerlo es muy sencillo:

1 app.set('version', '0.1.0');
2 app.get('version') // 0.1.0

Como ves, con set guardamos y con get obtenemos. ¡Genial! Además tenemos otros 3 métodos
relacionados…

1 app.enable('explosion_nuclear');
2 // Es lo mismo que
3 app.set('explosion_nuclear', true);
4
5 app.disable('explosion_nuclear');
6 // Es lo mismo que
7 app.set('explosion_nuclear', false);
8
9 app.enabled('explosion_nuclear'); // false
10 // Es lo mismo que
11 app.get('explosion_nuclear') === true
12
13 app.disabled('explosion_nuclear'); // true
14 // Es lo mismo que
15 app.get('explosion_nuclear') === false
Configuración de la aplicación 84

… que como ves, son atajos para ahorrarnos algo de texto.


El caso es que Express tiene algunos valores “secretos” que nos sirven además para modificar el valor
de la aplicación:

• view - Define el objeto de la vista. Éste no debemos sobreescribirlo bajo ningún concepto.
• Valores
– views - Define dónde se encuentra la carpetas donde estarán las vistas almacenadas. Por
defecto es la carpeta views en la raíz de tu aplicación.
– view engine - Define el motor de vistas que vamos a usar.
– jsonp callback name - Define el nombre de la función que se usa en respuestas JSONP
por defecto es callback.
– json spaces - Define el espacio de “tabulación” en JSON. Por defecto es 2 espacios si
estamos en desarrollo.
– env - Express se encarga de guardar por nosotros el entorno que definamos en la variable
de entorno NODE_ENV y por defecto es development si no definimos ninguno.
– subdomain offset - Define el número de partes que tiene tu dominio. Por defecto es dos
porque la mayoría de aplicaciones corren en dominios del tipo ejemplo.com pero cambia
si usamos algo como ejemplo.co.uk (malditos británicos)
• true o false
– x-powered-by - Define si Express usa la cabecera X-Powered-By:Express en las peticio-
nes.
– view cache - Define si Express guarda o no en caché las vistas compiladas, es true por
defecto si el entorno es production.
– case sensitive routing - Define si Express debe ser sensible a mayúsculas o minúsculas
en las rutas. Por defecto está desactivado por lo que /usuario y /UsUaRiO son idénticos.
– strict routing - Define si las rutas han de ser estrictas. Por defecto está desactivado
por lo que /usuario y /usuario/ son tratadas igual.
– trust proxy - Define soporte para proxy inverso que, por defecto, está desactivado.

Configurando la aplicación
Express es muy amigo de facilitarnos el trabajo y nos ofrece algunas formas de ahorrarnos caracteres.
La función configure es uno de esos ejemplos. Esta función nos permite configurar algún parámetro
dependiendo del entorno en el que nos encontremos. Veámoslo con un ejemplo:
Configuración de la aplicación 85

1 // Todos
2 app.configure(function(){
3 app.set('titulo', 'Mi aplicación');
4 });
5
6 // Solo desarrollo
7 app.configure('development', function(){
8 app.set('bdatos', 'localhost');
9 });
10
11 // Solo producción
12 app.configure('production', function(){
13 app.set('bdatos', '0.0.0.0');
14 });
15
16 // En mi casa
17 app.configure('casa', function(){
18 app.set('bdatos', 'localhost/miproyectosecreto');
19 });

De esta forma, podemos configurar ciertos parámetros de manera sencilla cuando usemos cualquier
entorno. El último ejemplo es una muestra de que no tenemos porque quedarnos con development
o production si no que podemos establecer cualquier cosa en la variable NODE_ENV.

Valores locales
Epxress nos permite establecer variables y/o funciones locales que serán accesibles por todas las
plantillas de nuestra aplicación. Muy útil si queremos tener valores como el título de la página o un
teléfono o algo así. Para ello, solo tenemos que colocarlas en el objeto app.locals.

1 app.locals.titulo = 'Mi aplicación';


2 app.locals.saludo = function(usuario) {
3 return "¡Hola " + usuario + "!";
4 }

Merece la pena destacar, que app.locals es también una función a la que podemos pasarle un objeto
que se encargará de “unir” a los datos que ya tenga. Por ejemplo:
Configuración de la aplicación 86

1 app.locals.titulo = 'Mi aplicacion';


2 app.locals({
3 saludar : function(usuario) {
4 return "¡Hola " + usuario + "!";
5 }
6 });
7
8 console.log(app.locals.titulo); // Mi aplicacion
9 console.log(app.locals.saludar('Manolo')); // ¡Hola Manolo!

Este tema quizá no tenga mucho sentido ahora mismo porque aun no hemos tratado las plantillas
pero volveremos sobre este tema más adelante.
Rutas
Ahh… las rutas… Las rutas son el auténtico corazón de Express. Estoy seguro de que no tardarás
mucho en comprenderlas ya que son bastante naturales de leer y entender.

Figura 6

Como ves es… bastante sencillo. Pero vamos a profundizar más. Estoy seguro de que al menos estás
acostumbrado a POST y a GET. Las otras dos, PUT y DELETE son parte de la especificación 1.1 del
protocolo HTTP. Hay más pero no se suelen usar: HEAD, TRACE, CONNECT y OPTIONS.
Por norma general, GET no debería provocar ningún cambio en los datos y es solo para obtener datos
mientras que POST, PUT y DELETE sí que modifican (o deberían) datos.
Lo habitual en estos días es usar la filosofía REST. No voy a profundizar mucho en esta filosofía,
pero vamos a repasar para qué se supone que sirve cada verbo:

• GET : Obtiene un recurso o una lista de éstos.


• POST : Inserta un recurso en una colección o crea un subordinado de un recurso.
• PUT : Actualiza una colección de recursos o un recurso en particular.
• DELETE : Borra un recurso o una lista de éstos.

Después del verbo viene la ruta que puede (o no) contener parámetros. Aunque éstos los veremos
un poco más adelante. Finalmente viene una función, una lista de ellas o incluso una matriz de
funciones. Esto quiere decir que todo este código es equivalente:
Rutas 88

1 function func1 () {
2 console.log('1');
3 }
4 function func2 () {
5 console.log('2');
6 }
7 function func3 () {
8 console.log('3');
9 }
10
11 app.get('usuario',func1,func2,func3);
12 // Equivalente a
13 app.get('usuario', [func1,func2,func3]);
14 // O solo usamos una
15 app.get('usuario',func1);

Las funciones solo pueden recibir 3 parámetros y sirven para controlar lo que ocurre. Podemos
decidir enviar una respuesta al usuario, enviar un error, pasar el código a la siguiente función de la
lista o… no hacer nada. Los tres parámetros son los siguientes:

• req : Del inglés request, que es la petición. En ella encontraremos datos de la petición tales
como las cabeceras que envía el navegador o los parámetros de la ruta si es que los hubiera.
• res : Del inglés response, que es la respuesta. Si no mandamos ninguna respuesta a la petición,
el navegador fallará porque lleva demasiado tiempo esperando respuesta o bien con un error
324 ERR_EMPTY_RESPONSE.
• next : Del inglés… ¡ohh wait! Viene a ser la siguiente función a la que llamar. Cuando nuestra
ruta termine su trabajo le puede pasar el testigo a la siguiente. Si pasamos un parámetro a la
llamada a next() significa que estaremos pasando un error.

Recuerda que puedes llamarlos como quieras pero es la forma habitual de llamarlos y así aparece en
todos los ejemplos de Express. Estos parámetros los veremos con mayor profundidad en un momento.
Veamos un sencillo ejemplo de ruta para ponernos en faena. Antes de seguir, me gustaría indicar
que este es el código que precede a cada ejemplo para evitar código innecesario:
Rutas 89

Ejemplo base de rutas

1 var express = require('express')


2 , routes = require('./routes')
3 , user = require('./routes/user')
4 , http = require('http')
5 , path = require('path');
6
7 var app = express();
8
9 // all environments
10 app.set('port', process.env.PORT || 3000);
11 app.set('views', __dirname + '/views');
12 app.set('view engine', 'jade');
13 app.use(express.favicon());
14 app.use(express.logger('dev'));
15 app.use(express.bodyParser());
16 app.use(express.methodOverride());
17 app.use(app.router);
18 app.use(express.static(path.join(__dirname, 'public')));
19
20 // development only
21 if ('development' == app.get('env')) {
22 app.use(express.errorHandler());
23 }
24
25 http.createServer(app).listen(app.get('port'), function(){
26 console.log('Express server listening on port ' + app.get('port'));
27 });

Vamos a ver una sencilla ruta, usemos GET para que podamos abrirlas en el navegador fácilmente.

1 app.get('/', function(req,res,next){
2 res.send(new Date());
3 });

Si arrancamos nuestro servidor y abrimos el servidor con el navegador, veremos que nos está dando
la hora… ¡justo como queríamos! Pero espera, que eso no es propio de un buen ejemplo…
Rutas 90

1 app.get('/', function(req,res,next){
2 res.send('¡Hola mundo');
3 });

Ahora sí… ¿ves? Puedes mandar cualquier dato al navegador como, por ejemplo, un objeto
JavaScript:

1 app.get('/', function(req,res,next){
2 res.send({
3 'hora' : new Date(),
4 'mensaje' : 'Hola mundo'
5 });
6 });

Ahora tenemos lo mejor de los dos mundos. Nuestro Hola mundo y la hora. ¿Y qué pasa si quiero
que todas las peticiones que haga, independientemente de si es GET o DELETE, me de la hora de esa
forma? Pues tenemos all:

1 app.all('/', function(req,res,next){
2 res.send({
3 'hora' : new Date(),
4 'mensaje' : 'Hola mundo'
5 });
6 });

De esta forma, cualquier tipo de petición que hagamos hará exactamente lo mismo.

¡Experimenta!

En ocasiones es complicado probar todo tipo de peticiones de manera sencilla. No


obstante, hay herramientas para ello. En Chrome podéis descargar Postman Rest Client³⁸
que nos permitirá crear cualquier tipo de petición y mandarla a cualquier punto.

Parámetros
Como ya he dejado ver a lo largo del capítulo, las rutas pueden contener parámetros. Lo ilustramos
con un ejemplo y ahora vemos:
³⁸https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm
Rutas 91

1 app.get('/usuario/:id', function(req,res,next){
2 res.send('Me has mandado: ' + req.params.id );
3 });

Si entramos en:
http://localhost:3000/usuario/123
Deberíamos ver en el navegador: Me has mandado: 123. Con :[nombre del parámetro],
podemos definir cualquier parámetor en nuestra URL. Podríamos incluso mezclarlos con estructuras
necesarias como:

1 /mensaje/:origen/a/:destino

Y quedarán disponibles dentro del objeto params que está dentro de req.
Además, podemos definir opciones para lo que podemos recibir en el parámetro. Por ejemplo:

1 app.get('/usuario/:id/:accion(editar|borrar)', function(req,res,next){
2 res.send('Me has mandado: ' + req.params.accion );
3 });

Solo responderá si el último parámetro es editar o borrar.

El orden de los factores altera el producto


Y mucho… supongamos que tenemos la ruta de antes. Y ahora ponemos justo debajo esta:

1 app.get('/usuario/test', function(req,res,next){
2 res.send('No me ejecuto');
3 });

Si ahora abres el navegador e intentas entrar en esa URL, verás que lo que aparece es: Me has
mandado: test. Esto es porque esa ruta también coincide con la ruta anteriormente definida y al
estar definida en primer lugar, entrará por ésta en lugar de la más específica. Es muy importante
tener esto en cuenta a la hora de diseñar las rutas y el orden en que las pongamos en la aplicación.

Parámetros opcionales
Es posible que, por el motivo que sea, queramos que un parámetro pueda ser opcional. Veamos cómo
lograrlo:
Rutas 92

1 app.get('/usuario/:id?', function(req,res,next){
2 var mensaje = req.params.id ? 'Me has mandado: ' + req.params.id : '¡No m\
3 e has mandado nada!';
4 res.send( mensaje );
5 });

Con la interrogación, el parámetro se convierte en opcional pudiendo así responder a estas peticiones:
/usuario/123 /usuario/
Incluso podemos mezclarlos:
/usuario/:id/:operacion? // Responde a /usuario/1 /usuario/1/borrar
O hacer cosas divertidas:
/usuario/:id.:formato? // Responde a /usuario/1 /usuario/1.json /usuario/1.zip …

Parámetro con expresión regular


Imagina que las IDs solo pueden ser numéricas y no queremos que si no es completamente numérico,
se llame a la ruta. Con nuestro primer ejemplo: /usuario/:id lo mismo da que pongamos 123 o que
pongamos manolito. ¿Cómo lo arreglamos? Con una pequeña expresión regular:

1 app.get('/usuario/:id(\\d+)', function(req,res,next){
2 res.send('Me has mandado: ' + req.params.id );
3 });

Esta sólo aceptará números mientras que \\w+ solo aceptará una cadena de caracteres. Esta es la
forma fácil. No obstante, Express convierte todas las rutas en expresiones regulares.
Por ejemplo:
/usuario/:id? =⇒ /\/usuario(?:\/([ \/]+?))?\/?$/i
Explicar las Expresiones Regulares queda fuera del alcance de este libro pero sin duda pueden ser
bastante poderosas. No obstante, Express tratará de forma diferente los parámetros dado que no hay
nombres si no, más bien, partes. Por ello, en vez de usar “nombres”, usamos números de 0 a n. En
nuestro ejemplo, los datos estarían en el 0, ya que es el primer parámetro.

1 app.get('/^\/usuario(?:\/([^\/]+?))?\/?$/i', function(req,res,next){
2 res.send('Me has mandado: ' + req.params[0] );
3 });

Elemento comodín
En ocasiones puede que queramos responder a una ruta y a todo lo que venga también por detrás.
Para eso tenemos el *.
Rutas 93

1 /usuario/*
2 // Responde a
3 /usuario/hola/que/tal/123

Hay que tener especial cuidado con este tipo de parámetros ya que pueden fastidiarte una ruta si
la pones al principio del todo. ¿Y cómo accedemos a lo que viene detrás? ¿Al valor de *? Para estos
casos haremos igual que cuando usamos las expresiones regulares:

1 app.get('/usuario/*', function(req,res,next){
2 res.send('Me has mandado: ' + req.params[0] );
3 });

Cadena de búsqueda
Es muy habitual que empecemos a construir rutas del tipo:

1 // /libros/:categoria/:autor
2 // /libros/:categoria/:autor/limit/:limit
3 // /libros/:categoria/:autor/skip/:skip
4 // /libros/:categoria/:autor/ordenar/:ordenarpor

Y la cosa comienza a complicarse. La gente comienza a olvidar los parámetros de búsqueda.


Imaginemos que queremos ofrecer libros por categoría. Podríamos usar esta URL:
/libros?categoria=terror
Y quizá queramos añadir el autor también:
/libros?categoria=terror&autor=stephen+king
¿Cómo accedemos a esos valores? Pues quedan guardados en el objeto query:

1 // /libros?categoria=terror&autor=stephen+king
2 app.get('/libros', function(req,res,next){
3 console.log(req.query.autor) // stephen king
4 console.log(req.query.categoria) // Terror
5 });

Incluso, podemos “agruparlos”:


Rutas 94

1 // /libros?categoria=terror&autor=stephen+king&libro[tipo]=bolsillo&libro[p\
2 recio]=<20
3 app.get('/libros', function(req,res,next){
4 console.log(req.query.autor) // stephen king
5 console.log(req.query.libro.tipo) // bolsillo
6 });
Middlewares
Uuhhh… eso da miedo. ¿Qué es un Middleware? Los Middleware, de manera sencilla, son funciones
que responden a las rutas.

¿Entonces, las funciones que hemos visto hasta ahora son Middlewares?

Técnicamente sí. No obstante, se les suele llamar Middleware a aquellas funciones por las que pasa
la ruta antes de llegar al final. Los Middleware pueden añadir información a la petición, terminarla
o pasar a la siguiente función con next(). Tenemos varias formas de usar los Middlewares pero, al
definirlos estamos determinando el orden en el cual se le da la oportunidad a cada middleware de
procesar la petición.
Veamos un ejemplo que yo creo que todos entenderéis sobre la importancia del orden de los
Middleware. Supongamos que tenemos esta ruta:

1 app.get('/usuarios.html', function(req, res, next) {


2 res.send('Te envío un listado de usuarios');
3 });

Si tenemos esto:

1 app.use(express.static(__dirname + '/public'));
2 app.use(app.router);

Esa ruta nunca se ejecutará si tenemos un archivo usuarios.html en la carpeta public ya que
express.static busca en el directorio y al encontrar el archivo, no pasa el testigo a la siguiente ruta,
si no que dice: “No os preocupéis, ¡ya lo tengo yo!” Es por eso que Express, por defecto, coloca el router
antes del Middleware que sirve los archivos estáticos. Para evitar que un nombre desafortunado
sobreescriba tu ruta.
Ahora que esto nos ha quedado más o menos claro, veamos cómo usar los Middleware.

app.use
Este es el ejemplo más básico de uso de MiddleWare y, lo que hace es añadir una función a la pila
de Middlewares. Ten en cuenta que usando app.use todas las rutas pasarán por ese Middleware.
Veamos un ejemplo de Middleware para evitar el acceso a nuestra aplicación desde Internet Explorer
8 o inferior:
Middlewares 96

helpers/ban_navegador.js

1 var banned = [
2 'MSIE',
3 'Trident/6.0'
4 ];
5
6 module.exports = function() {
7 return function(req, res, next) {
8 if (req.headers['user-agent'] !== undefined &&
9 req.headers['user-agent'].indexOf(banned) > -1) {
10 res.end('Navegador no compatible');
11 }
12 else { next(); }
13 }
14 };

app.js

1 var express = require('express')


2 , routes = require('./routes')
3 , user = require('./routes/user')
4 , http = require('http')
5 , path = require('path')
6 , banNavegador = require('./helpers/ban_navegador');
7
8 var app = express();
9
10 // all environments
11 app.set('port', process.env.PORT || 3000);
12 app.set('views', __dirname + '/views');
13 app.set('view engine', 'jade');
14 app.use(express.favicon());
15 app.use(express.logger('dev'));
16 app.use(express.bodyParser());
17 app.use(express.methodOverride());
18 app.use(banNavegador());
19 app.use(app.router);
20 app.use(express.static(path.join(__dirname, 'public')));
21
Middlewares 97

22 // development only
23 if ('development' == app.get('env')) {
24 app.use(express.errorHandler());
25 }
26
27 app.get('/', routes.index);
28 app.get('/users', user.list);
29
30 http.createServer(app).listen(app.get('port'), function(){
31 console.log('Express server listening on port ' + app.get('port'));
32 });

Nuestro pequeño Middleware revisa las cabeceras que envía el navegador y que están en el objeto
req para ver si coincide con alguna de las de Internet Explorer y, si es así, usa el método end() para
finalizar la petición, indicando que no es un navegador compatible. Como ves, lo colocamos antes
del router y del gestor de archivos estáticos, para que pueda actuar antes que ellos.
Esta forma de adjuntar Middlewares está recomendada para aquellos que queramos que se ejecuten
siempre. Pongamos que queremos guardar en un log todos los accesos de nuestra aplicación (que es
precisamente lo que hace express.loger), pues este es el mejor punto.

En línea
Otra forma que tenemos de pasar Middlewares es individualmente a cada ruta. Supongamos que
tenemos una función que valida si un usuario está o no logado en nuestra aplicación y queremos
proteger todo lo que vaya en la ruta admin

1 app.get('/admin', adminLogado, admin.index);


2 app.get('/admin/usuarios', adminLogado, admin.usuarios);
3 app.get('/admin/libros', adminLogado, admin.libros);

En este caso, la función adminLogado, se ejecutará antes de ejecutar la función final y, por tanto,
todas las rutas quedan efectivamente protegidas por adminLogado.
Otra forma de hacerlo sería esta:

1 app.get('/admin/*', adminLogado);
2 app.get('/admin/', admin.index);
3 app.get('/admin/usuarios', adminLogado, admin.usuarios);
4 app.get('/admin/libros', adminLogado, admin.libros);

Ya que adminLogado tiene que llamar a next(), pasará el testigo a la siguiente función que le toque,
en función de la ruta a la que estemos llamando.
Middlewares 98

Mapeado
Express nos permite “escuchar” cuando estamos pidiendo algún elemento en una ruta. Imagina que,
cuando estás entrando a una ruta con :id_usuario pudieras llamar automáticamente a una función
que trajera al usuario de la base de datos y pasarlo con los datos de la petición. Deja de imaginarlo…
¡Es real! Este tipo de middleware es realmente útil ya que te ahorran bastante trabajo.
Veamos un ejemplo:

1 app.param('id_usuario', function(req, res, next, id){


2 Usuario.find(id, function(err, usuario){
3 if (err) {
4 next(err);
5 } else if (user) {
6 req.params.usuario = usuario;
7 next();
8 } else {
9 next(new Error('Error al cargar el usuario'));
10 }
11 });
12 });

En este ejemplo estamos asumiendo que Usuario forma parte de algún ORM que se encargue de
hacer las operaciones de lectura, actualización y borrado de una base de datos. Al colocar este código,
le estamos diciendo a Express:

Cada vez que en una ruta esté id_usuario, tráetelo de la base de datos y me lo dejas en
la petición.

¿No es conveniente?

En resumen
Vamos a intentar resumir cuándo es bueno usar cada método:

• app.use : Usaremos este método cuando queramos que todas las rutas sean filtradas por este
Middleware.
• En línea : Usaremos este método cuando queramos que ciertas rutas sean filtradas.
• Mapeado : Usaremos este método cuando queramos que al usar un parámetro específico en
la ruta, éste sea tratado por la aplicación y asociado así a la petición.

Por último, recalcar nuevamente el orden de los Middlewares. Éstos se ejecutan en el mismo orden
en que hayan sido definidos por lo que es fundamental tener esto en cuenta a la hora de asignarlos.
Middlewares 99

Middlewares ofrecidos por Express


Express nos ofrece 7 Middlewares para usar. Aunque algunos los hayamos visto ya, no está de más
repasarlos todos para ver lo que nos pueden ofrecer.

basicAuth
Este Middleware nos ofrece una protección básica de usuario y contraseña para nuestras rutas. De
manera que nos solicitará ambos datos y, si no los ofrecemos, no podremos ejecutar la ruta:

1 app.use(express.basicAuth('usuario', 'contraseña'));

En el caso de que queramos algo un poco más complejo y consultemos el usuario y la contraseña en
la base de datos, seguramente estemos hablando de asincronía, para lo cual cambiamos un poco el
esquema:

1 app.use(express.basicAuth(function(usuario, password, callback){


2 Usuario.login({ usuario: usuario, password: password }, callback);
3 }))

Lo que hacemos es pasar la función callback para que una vez nos traigamos los datos de la base
de datos, podamos invocarla. A la función hay que pasarle un valor true o false.

bodyParser
Este Middleware es el encargado de procesar el cuerpo de las peticiones que llegan a nuestra
aplicación. Para que nos entendamos, es el encargado de que una petición POST coloque los
parámetros en req.params o nos facilite los archivos de haberlos.

1 app.use(express.bodyParser());

compress
Si os habéis movido por Internet y conocéis algunas librerías de JavaScript, como Ember³⁹, veréis
que pone algo así como min + gzip. compress se encarga de comprimir los datos enviados para que
las comunicaciones sean más ligeras en la medida de lo posible. En concreto se encarga de la parte
gzip.
Este Middleware debe ir en la parte superior de la cadena para que la mayoría de los datos sean
capturados por éste.
³⁹http://emberjs.com
Middlewares 100

1 app.use(express.logger());
2 app.use(express.compress());
3 app.use(express.methodOverride());
4 app.use(express.bodyParser());

cookieParser
Este Middleware se encarga de parsear las cookies y de rellenar req.cookies con los nombres de las
cookies. Para mejorar la seguridad, podemos activar el firmado de cookies añadiendo una palabra
secreta a ésta.

1 app.use(express.cookieParser());
2 app.use(express.cookieParser('palabra super secreta');

session
Quizá te lo puedas imaginar, pero este Middleware se encarga de añadir soporte para sesiones en
Express. Si algún Middleware hace uso de los valores que almacenes en sesión, ten en cuenta que
deberás colocarlos bajo la declaración de este Middleware.

1 app.use(express.session());

cookieSession
cookieSession nos permite tener cookies basadas en la sesión del usuario y se encarga de rellenar
req.session con esa información. Éste puede recibir 4 parámetros:

• key : Indicador de la cookie. Por defecto es connect.sess


• secret : Evita la alteración de cookies por externos al añadir una palabra secreta.
• cookie : Cambia la configuración de la cookie que por defecto es:
– path : /
– httpOnly : true
– maxAge : null
• proxy : Confía en el proxy inverso al establecer cookies seguras.

1 app.use(express.cookieSession());

Dado que las Cookies se guardarán en sesión, podremos deshacernos de ellas estableciendo
req.session a null antes de enviar la respuesta.
Middlewares 101

csrf
Este Middleware nos ofrece protección contra Cross-site request forgery (falsificación de petición
en sitios cruzados).
Este Middleware se encarga de generar un token llamado _csrf que debemos añadir a las peticiones
que queramos proteger dentro de un campo de formulario oculto, una cadena de consulta, etc. El
Token será validado contra el valor de req.session._csrf. En caso de que no sean iguales fallará.
El uso de csrf necesita el uso de la sesión por lo que tendremos que colocarlo bajo el uso de
session().

static
Este Middleware es de los más básicos de Express y, como ya sabes, se encarga de servir el contenido
estático de un directorio (y sus subdirectorios).

1 app.use(express.static(__dirname + '/public'));

directory
Este Middleware se encarga de servir un directorio. Este comportamiento muy habitual en servidores
Apache está desactivado por defecto en Express. Básicamente lo que hace es que si tenemos por
ejemplo el Middleware:

1 app.use(express.static(__dirname + '/public'));

Y tenemos la carpeta /public/imagenes/portal, podremos ver sin problemas la imagen /public/imagenes/portal/


pero si accedemos directamente al directorio, obtendremos un error.
Para subsanarlo, tenemos que colocar este Middleware antes del estático:

1 app.use(express.directory(__dirname + '/public'));
2 app.use(express.static(__dirname + '/public'));

Podemos pasarle algunas opciones al Middleware:

• hidden : Indica si muestra o no los archivos ocultos del directorio. Por defecto es false.
• icons : Indica si muestra o no los iconos del directorio. Por defecto es false.
• filter : Aplica el filtro definido en este parámetro (que ha de ser una función) a los archivos.
Por defecto es false.
La petición - request
La gran mayoría de las cosas interesantes sobre la petición, ya las hemos visto cuando hemos hablado
de las rutas. No obstante, voy a detenerme en alguno de los métodos que ya hemos visto y en otros
que considero importante detenerme.
No vamos a tratar todos las funciones del objeto request ya que para eso tenemos el API⁴⁰.

req.body
Cuando enviamos una petición del tipo que sea al servidor, gracias al uso de bodyParser, todos los
parámetros quedan almacenados en este objeto. Imaginemos que tiene un formulario de este tipo:

1 <form method="post" action="/login">


2 <label for="usuario">Usuario</label>
3 <input type="text" id="usuario" name="usuario"/>
4 <label for="password">Contraseña</label>
5 <input type="password" id="password" name="password"/>
6 </form>

En la ruta que se encarga de gestionar el formulario, en este caso por el método POST, tendremos
acceso a los valores de usuario y password dentro de este objeto:

1 app.post('/login',function(req,res){
2 console.log('Usuario "%s" intenta acceder al sistema',req.body.usuario);
3 });

req.param(parametro)
Express nos ofrece un método para acceder a cualquier parámetro. Hasta ahora, hemos visto que
tenemos 3 tipos de parámetros (lamento desilusionarte, no hay más):

• req.params : Los que van dentro de la definición de la ruta (por ejemplo: /usuarios/:id)
• req.query : Los que van dentro de la cadena de consulta de la URL (por ejemplo /usuarios?id=1234)
• req.body : Los que van dentro de la propia petición
⁴⁰http://expressjs.com/api.html#request
La petición - request 103

Con req.param('usuario'), Express se encarga de buscar su valor en los tres sitios por este orden:

1. req.params
2. req.body
3. req.query

No obstante, no es del todo recomendable el uso de esta función a no ser que sea totalmente claro
ya que puede prestar a confusión.

req.is(tipo)
Este método nos permite comprobar el tipo de datos que nos llega en la petición. Imagina que quieres
comprobar si la petición envía datos del tipo JSON:
Normalmente, el tipo del contenido enviado al hacer esta petición, sería application/json.
Express nos permite comprobarlo con esta función:

1 req.is('json'); // true
2 req.is('application/*'); // true
3 req.is('application/json'); // true
4 req.is('html'); // false

req.ip
Es muy habitual el guardar en una aplicación a última IP desde la que se ha logado un usuario por
ejemplo, o mantener una lista de IPs conectadas a nuestra aplicación.
Express se encarga de guardar la IP de la petición directamente en la propiedad ip dentro del objeto
req.

req.xhr
Esta propiedad es bastante útil ya que nos permite saber si es una petición del tipo AJAX.
Básicamente, cuando usamos alguna función AJAX con jQuery por ejemplo, se envía la cabecera
X-Requested-With con el valor XMLHttpRequest.

Si revisamos el valor de req.xhr será true si es una petición de este tipo. Esto te puede ser de utilidad
para capar ciertas rutas que sepas que solo vas a usar con AJAX para que no sean accesibles desde
un navegador (fácilmente claro).
La respuesta - response
Cuando modificamos el objeto res, estaremos modificando lo que se va a enviar de vuelta cuando
se recibe una petición. Por ello, has de tener especial cuidado ya que si bien los datos de req son más
de consulta, los de res se envían de vuelta al usuario (normalmente).

res.status
Esta función nos permite modificar el estado que mandamos de vuelta al navegador. Por defecto es
un estado 200 que significa que todo es correcto.
Estos son algunos de los estados más habituales:

Código Descripción
200 Respuesta estándar para peticiones correctas
301 La URL ha cambiado. Inicia redirección normalmente
401 No autorizado, posiblemente haya que iniciar sesión
404 Recurso no encontrado
500 Error interno del servidor

Si quieres verlos todos, te recomiendo que le eches un vistazo a este Anexo de la Wikipedia⁴¹.
Ten en cuenta que poner res.status(404) no hace que la petición se envíe si no que has establecido
que esa es la cabecera que recibe el navegador.

res.redirect
Con este método podemos realizar una redirección. La redirección puede ser a una URL completa
(como por ejemplo google) o a una ruta en nuestra aplicación. Este método sí que termina la respuesta
y, si la redirección es a nuestra aplicación, Express volverá a hacerse cargo de ella… en otro punto
claro.
Imagina que tienes un Middleware para comprobar si el usuario está logado y, si no lo está, lo
redirigimos a login:

⁴¹http://es.wikipedia.org/wiki/Anexo:C%C3%B3digos_de_estado_HTTP
La respuesta - response 105

estalogado.js
1 module.exports = function(req,res){
2 if (req.params.usuario.logado){
3 next();
4 }
5 else {
6 res.redirect('/login');
7 }
8 };

res.send
Esta es la función que se encarga realmente de enviar una respuesta. Además, para facilitarnos la
tarea, incluso podemos establecer un código de estado asociado a la respuesta.

1 res.send(404, 'Oops... me da que no está por aquí');

Si queremos enviar JSON, con pasar una matriz o un objeto JavaScript, Express se encargará de
establecer las cabeceras de respuesta correcta:

1 res.send({ fecha : new Date() });

Si por lo que sea no mandamos texto ni objeto pero mandamos un número, Express se encargará de
asignar el texto por nosotros. Por ejemplo, para 404 pondrá Not found, para 200 pondrá OK, etc.

res.jsonp
Ya hemos visto que podemos mandar JSON desde Express pasando un objeto JavaScript o una
matriz. Además, podemos valernos de res.json para pasar datos como null o undefined que no
son técnicamente válidos como JSON.
No obstante, ¿qué pasa si queremos enviar JSONP?
Pues que tendremos que valernos de esta función:

1 res.jsonp({ fecha : new Date() });

Recuerda que la función usada por defecto es callback y que para cambiarla tendremos que usar el
ajuste de la aplicación:
La respuesta - response 106

1 app.set('jsonp callback name', 'mifuncion');

Sobre JSONP

Si todo esto de JSONP te suena a chino, te recomiendo que le eches un vistazo a este
artículo en Función 13⁴² sobre las peticiones Ajax Cross-Domain.

res.sendfile
Si queremos enviar un archivo al navegador para visualizarlo, esta es la función que debemos usar
ya que lo que hace es enviar el archivo como respuesta.
Veamos un sencillo ejemplo:

1 app.get('/libros/:id', function(req, res){


2 var id = req.params.id,
3 ruta_libro = '/libros/' + id + '.pdf';
4
5 fs.exists(ruta_libro,function(existe){
6 if (existe){
7 res.sendFile(ruta_libro);
8 }
9 else {
10 res.send(404,'Libro no encontrado');
11 }
12 });
13 });

En este sencillo ejemplo estamos comprobando primero si el libro existe. En caso de ser así, se envía
la ruta del libro para que Express le indique al navegador que nos muestre el archivo PDF.
En caso de no existir, enviamos un error 404 ya que el libro, no existe.

res.download
¿Pero qué pasa si lo que quiero es descargarme el libro?
⁴²http://www.funcion13.com/2012/04/12/como-realizar-peticiones-ajax-cross-domain-jsonp-jquery/
La respuesta - response 107

1 app.get('/libros/:id', function(req, res){


2 var id = req.params.id,
3 ruta_libro = '/libros/' + id + '.pdf';
4
5 fs.exists(ruta_libro,function(existe){
6 if (existe){
7 res.download(ruta_libro,'libro.pdf');
8 }
9 else {
10 res.send(404,'Libro no encontrado');
11 }
12 });
13 });

Pues que podemos descargarlo con res.download. Esta función además acepta un segundo paráme-
tro en el que le podemos indicar el nombre que queremos usar para la descarga, pudiendo así darle
un nombre más bonito.

res.render
Esta función es la que se encarga de decirle al motor de vistas que nos muestre una. Aunque
enseguida trataremos sobre Jade, el motor por defecto de Express, vamos a ver por encima esta
sencilla función.

1 res.render('miPlantillaDeFechaYHora', { fecha: new Date() });

La función recibe el nombre de la vista a mostrar, un objeto con parámetros para la vista, y una
función de callback siendo los dos últimos parámetros opcionales.
Ahora que ya hemos llegado al “final” sobre la respuesta… ¿qué te parece si nos metemos de lleno
con las plantillas?
Plantillas con Jade
Creo que lo he comentado el algún momento pero… Jade es el sistema de plantillas por defecto de
Express. Seguramente te estés preguntando qué es una plantilla y para qué sirven. ¡Vamos a verlo!
Las plantillas te permiten escribir el código mínimo necesario para que una página muestre HTML.
Los motores de plantillas nos permiten hacer cosas chulas como anidar plantillas, ejecutar código
en ellas, etc.
Para empezar con Jade, antes qtengo que hacer una advertencia. Jade permite la indentación con
tabs o con espacios pero, uses la que uses, no puedes mezclarla con la otra. Además, las plantillas de
Jade, tienen extensión .jade.
Lo primero que vamos a hacer es echarle un vistazo al archivo layout.jade que Express creó por
nosotros cuando creamos nuestra aplicación de prueba:

layout.jade

1 doctype 5
2 html
3 head
4 title= title
5 link(rel='stylesheet', href='/stylesheets/style.css')
6 body
7 block content

En Jade, todo funciona con tabulaciones/espacios y aunque al principio es un poco raro, te acabas
acostumbrando. Si te fijas, doctype y html están al mismo nivel. Esto quiere decir que están en el
mismo punto del árbol. Sin embargo, head está dentro de html junto con body. A su vez, dentro de
head tenemos a title y una hoja de estilos.
title es igual a title… ¡Valiente perogrullada! Lo que realmente quiere decir es que el segundo
title lo coje de la propiedad title del objeto que le pasamos a Express como segundo parámetro
a la hora de renderizar una ruta. Recordemos:

1 exports.index = function(req, res){


2 res.render('index', {title : 'Express'});
3 };

Aprovecho también para recordar, que podríamos borrar ese objeto y ponerlo como parámetro local
de la aplicación:
Plantillas con Jade 109

1 app.locals.title = 'Express';

Si pasamos además un parámetro title igualmente, este sobreescribirá al valor local de la aplicación.
En body, directamente cargamos un bloque que Express llama content. Lo veremos ahora en cuanto
le pongamos un ojo a index.jade.

layout.jade

1 extends layout
2
3 block content
4 h1= title
5 p Welcome to #{title}

Este archivo es mucho más corto. Lo primero que destacamos es que dice extends layout y aunque
seguro que te lo estás imaginando, sí: significa que extiende a layout.jade.
El bloque content vuelve a aparecer, para indicar al motor dónde debe colocar el contenido. Así
pues, el resto del archivo pende del bloque content.
Una vez más, vemos un elemento h1 cuyo valor es el de la variable title y en el párrafo volvemos
a usarlo.

Sintaxis básica
Jade fue creado por el mismo programador que Express así que su documentación es igual de buena.
No obstante, he de reconocer que ha ido mejorando bastante con el tiempo.
La documentación, y la página web oficial de Jade es esta: http://jade-lang.com/
Vamos a ver qué podemos hacer.

Creación de nodos
En Jade podemos crear un nodo simplemente poniendo la palabra. Si queremos crear un elemento
h1 pues ponemos símplemente:

1 h1 Soy un gran título


2 // Se traduce en <h1>Soy un gran título</h1>

¿Y me puedo inventar un nodo y poner por ejemplo esto?


Plantillas con Jade 110

1 homer prueba
2 // Se traduce en <homer>Prueba</homer>

Como poder poder, lo que se dice poder… pues se puede. Pero realmente no tiene mucho sentido.
Jade se encarga de generar automáticamente las etiquetas de apertura y cierre por nosotros. Como
norma general, cada línea de Jade comienza con el nombre de un elemento y un espacio y cualquier
texto o atributos que añadir al atributo.
Como ya hemos visto, si tabulamos, los elementos se meten dentro del padre.

Añadiendo atributos a los nodos


La regla dice que para añadir un atributo a un nodo, simplemente lo metemos entre paréntesis así:

1 a(href='#', title='Descarga ahora', class='enlace') Descargar


2 // Se traduce en <a href="#" title="Descarga ahora" class="enlace">Descarga\
3 r</a>

Seguramente ahora te estés preguntando, si has mencionado eso de la regla es que hay cosas que se
salen de la regla, ¿no? Efectivi wonder
Para la id y la clase (class), no tenemos porqué meterlo entre paréntesis:

1 a#descarga_ya.enlace(href='#') Descargar
2 // Se traduce en <a id="descarga_ya" href="#" class="enlace">Descargar</a>

Para poner la id, solo tenemos que precederla de la almohadilla # y la clase va precedida de un ..
No, no es casualidad, es igual que ocurre en CSS.

¿Y qué pasa si quiero añadir más de una clase?

1 a#descarga_ya.enlace.gigante(href='#') Descargar
2 // Se traduce en <a id="descarga_ya" href="#" class="enlace gigante">Descar\
3 gar</a>

Aprovechando que estamos saliéndonos de la regla para destacar algo. Si no ponemos un elemento
pero ponemos una id y/o clase, Jade creará una div por nosotros, puesto que es el que han
considerado como elemento más básico.
Plantillas con Jade 111

1 #mi-div
2 // Se traduce en <div id="mi-div"></div>
3 ~~~~~~
4
5 ### Añadiendo texto
6
7 Para añadir texto a un bloque, solo tenemos que dejar un espacio junto al n\
8 ombre del bloque, y poner el texto que nos apetezca:
9
10 {lang=text}

p Vehicula Tristique Ipsum Dolor // Se traduce en <p>Vehicula Tristique Ipsum Dolor</p> ∼∼∼∼∼∼∼
¿Pero qué pasa si queremos añadir mucho texto y queremos darle un espacio o algo? Para eso
tenemos dos opciones:

1 p
2 | En un lugar de la Mancha,
3 | de cuyo nombre no quiero acordarme,
4 | no ha mucho tiempo que vivía un hidalgo
5 | de los de lanza en astillero, adarga antigua,
6 | rocín flaco y galgo corredor.
7 // Se traduce en
8 <p>En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha much\
9 o tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua,\
10 rocín flaco y galgo corredor.</p>

Si interponemos la barra vertical, Jade entiende que es texto que pertenece al anterior. Si la barra
vertical te resulta molesta, tenemos otra opción:

1 p.
2 En un lugar de la Mancha,
3 de cuyo nombre no quiero acordarme,
4 no ha mucho tiempo que vivía un hidalgo
5 de los de lanza en astillero, adarga antigua,
6 rocín flaco y galgo corredor.
7 // Se traduce en
8 <p>En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha much\
9 o tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua,\
10 rocín flaco y galgo corredor.</p>

Al poner un punto justo después del elemento, el efecto es el mismo. Ten en cuenta que si colocas
un espacio entre el punto y el elemento, Jade lo interpretará como texto.
Plantillas con Jade 112

Quizá no le veas mucho interés ahora mismo pero… ¿cómo pondrías una etiqueta label con texto
y un checkbox dentro? ¡Vamos a verlo!

1 label
2 | Jade mola?
3 input(type="checkbox",checked=true)
4 // Se traduce en
5 <label>Jade mola?<input type="checkbox" checked></label>

En los elementos script y style no es necesario usar estos métodos ya que solo puede ir texto
dentro de ellos.

Anidando elementos
Ya hemos visto la anidación básica de elementos que consguimos al tabular:

1 ul
2 li
3 a(href="#") Uno
4 // Se traduce en
5 // <ul>
6 // <li><a href="#">Uno</a></li>
7 // </ul>

No obstante, Jade nos ofrece una pequeña ayuda para no tener que usar tantos saltos y tabulaciones:

1 ul
2 li:a(href="#") Uno
3 // Se traduce en
4 // <ul>
5 // <li><a href="#">Uno</a></li>
6 // </ul>

Los dos puntos : indican que el bloque es hijo del que lo precede.

Variables en Jade
Como hemos visto al principio, podemos pasarle un objeto a la plantilla de Jade, y hacer que sus
propiedades sean variables dentro de la plantilla. Imagina que estás creando un formulario de edición
Plantillas con Jade 113

de un usuario, lo normal es que esos datos vengan rellenos y se los pases a la plantilla para que ésta
los “rellene” por nosotros.
Tenemos dos formas de poner variables en los bloques, cada una para un caso distinto.
Si queremos que un bloque sea igual al valor de una variable:

1 // nombre = 'Antonio'
2 h1= nombre
3 // Se traduce en <h1>Antonio</h1>

No obstante, si lo que queremos es que la variable forme parte del texto, tendremos que usar esta
otra sintaxis:

1 // nombre = 'Antonio'
2 h1 Hola #{nombre}
3 // Se traduce en <h1>Hola Antonio</h1>

La almohadilla y las llaves indican que lo que hay dentro es una variable y por tanto Jade intentará
evaluarlas. También podemos usarlo en las etiquetas de los elementos.

1 // id = '1234'
2 // nombre = 'Antonio'
3 a(href='/usuarios/' + id)= nombre
4 a(href='/usuarios/#{id}')= nombre
5 // Se traduce en <a href="/users/1234">Antonio</a>

En el caso de las etiquetas, podemos usar + también para concatenar cadenas, como lo haríamos en
JavaScript.

Variables opcionales
Una de las cosas que uno descubre más tarde o más temprano, es que si intentas renderizar una
plantilla con una variable que no estás mandando, dará un error 500 diciendo ReferenceError y
dirá que la variable no está definida.
Tenemos dos formas de lidiar con esto.
La primera es, usar un objeto por defecto con las propiedades definidas pero vacías. Para ello, vamos
a valernos de un módulo que se llama xtend y que extiende los objetos. De la misma forma que
lo hace $.extendde jQuery. Es decir, si dos (o más) objetos tienen propiedades idénticas, la que se
preserva es la del objeto más a la derecha. Veamos un rápido ejemplo:
Plantillas con Jade 114

1 var extend = require("xtend");


2
3 var usuarioDefecto = {
4 nombre : '',
5 apellidos : '',
6 movil : '',
7 correo : ''
8 };
9
10 var usuario = {
11 nombre : 'Antonio',
12 apellidos : 'Laguna Matías',
13 correo : 'sombragriselros@gmail.com'
14 };
15
16 console.log(extend(usuarioDefecto,usuario));
17
18 // { nombre: 'Antonio',
19 // apellidos: 'Laguna Matías',
20 // movil: '',
21 // correo: 'sombragriselros@gmail.com' }

Como ves, a pesar de que no hemos pasado la propiedad movil, hemos extendido del usuario por
defecto y hemos puesto un valor (vacío). Con lo que Jade ya no fallará si no le pasamos alguna de
las propiedades.
La segunda forma, es la de evaluar la existencia de la variable dentro de la plantilla. Veamos cómo
hacerlo:

1 if locals.movil
2 p
3 strong Móvil
4 | #{movil}
5 else
6 p
7 strong Móvil

En caso de que el la variable movil no esté definida, no tendremos un error. Las propiedades del
objeto que pasemos, quedan guardadas dentro del súper objeto locals por lo que, de esta forma,
podemos evaluar si la variable está o no definida.
Plantillas con Jade 115

Bloques de código auxiliares


Jade nos ofrece algunos bloques de código que podemos usar para modificar el comportamiento de
la plantilla. Éstos son bastante útiles en ciertos momentos.

if, else y unless


Aunque ya lo hemos visto de pasada, el bloque if con else nos permite evaluar si ciertas condiciones
son ciertas o falsa y así actuar en consecuencia. Ten en cuenta que el uso es exactamente el mismo
que en JavaScript regular.
Además de if, Jade nos ofrece el bloque unless (a menos qué) que es equivalente a if (!
(expresión)):

1 unless usuario.edad > 18


2 p No puedes ver aun esta página

case
El bloque case es exactamente lo que esperas que sea, evalúa una variable y muestra algo en función
del valor de la variable. Veámoslo con un ejemplo sacado directamente de la documentación.

1 case amigos
2 when 0: p No tienes amigos
3 when 1: p Tienes un amigo
4 default: p Tienes #{amigos} amigos

Jade se encargará de evaluar la variable amigos y, en este caso, actuará de 3 formas diferentes. En
caso de que el valor no sea o 0 o 1, caerá en default.

Iteración sobre matrices y objetos


Jade nos permite iterar de manera sencilla sobre matrices y objetos para crear marcado por nosotros.
Supongamos que esto es lo que le pasamos res.render:
Plantillas con Jade 116

1 {
2 usuario : {
3 nombre : 'Antonio',
4 correo : 'sombragriselros@gmail.com'
5 },
6 intereses : ['PHP','JavaScript','HTML5','Jade']
7 }

Veamos cómo podemos mostrar esto en Jade

1 ul
2 each interes in intereses
3 li=interes
4 // Se convierte en
5 // <ul>
6 // <li>PHP</li>
7 // <li>JavaScript</li>
8 // <li>HTML5</li>
9 // <li>Jade</li>
10 // </ul>

1 ul
2 each valor,propiedad in usuario
3 li
4 strong #{propiedad}:
5 | #{valor}
6 // Se convierte en
7 // <ul>
8 // <li><strong>nombre:</strong> Antonio</li>
9 // <li><strong>correo:</strong> sombragriselros@gmail.com</li>
10 // </ul>

Con el la instrucción each podemos iterar sobre matrices y objetos fácilmente. Incluso podemos
obtener el valor de la propiedad y/o el índice del elemento en la matriz como acabamos de ver,
añadiendo una , y otra palabra para indicarlo. Si each no te gusta, puedes intercambiarlo por for
que hace lo mismo.

Añade contenido a un bloque


¿Os acordáis de block? Los bloques son la forma que tenemos de reusar plantillas. Imaginemos que
tienes esta extructura:
Plantillas con Jade 117

1 html
2 head
3 block head
4 style(src='/main.css')
5 body
6 block contenido

Ahora, estás en la plantilla de registro, y quieres incluir en una de esas librerías tan chachis para
validar formulario y necesitas incluir una plantilla adicional. ¿Las añades para todas? Es una opción.
Pero también podemos añadir contenido a esos bloques:

1 extends layout
2
3 block append head
4 style(src='/validador-molon.css')

Con append añadimos el contenido que metamos al final del bloque. Si usamos prepend por el
contrario, añadiremos contenido al principio del bloque.
Páginas de error en Express
Ahora que ya tenemos un poco de conocimiento sobre Jade, podemos crear unas páginas personali-
zadas para un error 404 o 500 (que suelen ser los más comunes). En el caso del Error 500 vemos una
traza de error que puede que no nos interese que el usuario vea, y en el caso de un error 404 la cosa
es aun peor porque lo que vemos es CANNOT GET /…
¿Se te ocurre alguna forma de abordar el problema?

¿No? Está bien. ¡Con Middlewares!
Lo que vamos a hacer es crear dos Middlewares que se encarguen de gestionar los errores 404 y 500.
Añádelos justo después del último Middleware. Ahora veremos porqué:

1 // Error 404
2 app.use(function(req, res) {
3 res.status(400);
4 res.render('404', { title: '404 - No encontrado' });
5 });
6
7 // Error 500
8 app.use(function(error, req, res, next) {
9 res.status(500);
10 res.render('500', {
11 title: 'Oops… ¡Algo salió mal!',
12 error: error
13 });
14 });

404.jade

1 extends layout
2 block content
3 h1= title
4 p Vaya, parece que aquí no está lo que estabas buscando
5 a(href='/') Volver a la página de inicio
Páginas de error en Express 119

500.jade

1 extends layout
2 block content
3 h1= title
4 p Ha ocurrido un error interno: #{error}

Las plantillas Jade como ves son realmente sencillas y no tienen mucho misterio conforme a lo que
hemos visto hasta ahora. Pero, ¿cómo funcionan esos dos Middlewares?
Cuando Express no encuentra a ninguna ruta a la que pasarle la petición dentro de app.route,
comienza a seguir por todos los Middlewares que hayamos definido por debajo. Normalmente
buscará primero si puede servir algún archivo estático con app.use(express.static …. Si tampoco
lo consigue, entrarán en juego estos dos Middlewares que hemos creado.
El último llega en caso de que se genere un error en una ruta ya que se pasa como parámetro.
¿Por qué ponemos como error 400? El error 400 significa que la petición ha sido errónea. Algunos
navegadores muestran una página personalizada del propio navegador si el estado es 404, por lo que
así nos cubrimos las espaldas.
Por supuesto, se puede (y quizá debe) quitar el error de la página 500 para que el usuario no vea
“las tripas” de lo que ha pasado. Pero quizá sea un buen momento para que el administrador (espero
que tú, querido lector) reciba un correo indicando el error que se ha producido para evitar que se
produzca nuevamente.
Gestión de Login con Passport
Una de las cosas más habituales que implementamos en la mayoría de las aplicaciones, es un poco
de seguridad. Y con ello me quiero referir a Login principalmente. En la mayoría de las ocasiones,
tendremos partes de una aplicación que no queremos que sean accesibles a no ser que se haya
iniciado sesión.
Passport es un genial Middleware para Express que nos ayudará a implementar un sistema de
autenticación en nuestro sistema sin muchos problemas. Passport nos ofrece diferentes estrategias
para autenticar y nos ofrece multitud de ventajas que en seguida veremos.
Te animo a visitar su web⁴³ para echar un vistazo a las posibilidades que ofrece.
Lo primero que haremos será instalarlo con la facilidad habitual:

1 $ npm install passport

Además, para nuestro pequeño ejemplo, vamos a tener que instalar una estrategia. Las estrategias
(como Passport las llama) no son otra cosa que métodos que tenemos para autenticarnos en la
aplicación. Una estrategia puede ser un usuario local y otra puede ser Twitter, Facebook o Google.
Para nuestro ejemplo, vamos a usar la autenticación local, esto es, sin contar con servicios de terceros.
Para ello, necesitamos instalar dos cosillas:

• connect-flash : Este módulo nos permite pasar mensajes de error con el inicio de la sesión.
• passport-local : Este módulo es, en sí, la estrategia local que nos vamos a encargar de
configurar.

Añádelos a tu archivo package.json tal que así:

1 "dependencies": {
2 "express": "3.2.5",
3 "connect-flash" : "0.1.x",
4 "passport" : "0.1.x",
5 "passport-local" : "0.1.x",
6 "jade": "*"
7 }

Y luego escribe:
⁴³http://passportjs.org/
Gestión de Login con Passport 121

1 $ npm install

Ahora que tenemos todo instalado y listo para usar en nuestra aplicación, vamos a ver como hacerlo.
Lo habitual en estos casos es sacar los datos del usuario de una base de datos, pero dado que eso
queda un poco fuera del alcance de este libro, vamos a suponer que los usuarios están en una matriz.

1 var usuarios = [
2 { nombre : 'Antonio', usuario : 'alaguna', password : '12345' },
3 { nombre : 'Test', usuario : 'test1', password : '12345' },
4 { nombre : 'Test', usuario : 'test2', password : '12345' },
5 { nombre : 'Test', usuario : 'test3', password : '12345' },
6 ];

Veamos primero la estructura de carpetas que vamos a usar:

Figura 7: Estructura de la carpeta de la aplicación

Vamos primero a nuestro archivo estrategia-local ya que en la aplicación haremos uso de sus
funciones:

lib/passport/estrategia-local.js

1 var EstrategiaLocal = require('passport-local').Strategy;


2
3 module.exports = function(usuarios){
4
5 function encontrarUsuario(usuario, callback){
6 for (var i = 0, lon = usuarios.length; i < lon; i++){
7 var usuarioLocal = usuarios[i];
8 if (usuario === usuarioLocal.usuario){
Gestión de Login con Passport 122

9 return callback(null, usuarioLocal);


10 }
11 }
12 return callback(null, null);
13 }
14
15 function aseguraLogin(req, res, next) {
16 if (req.isAuthenticated()) {
17 return next();
18 }
19 req.session.rutaLogin = req.route.path;
20 res.redirect('/login')
21 }
22
23 function serializarUsuario(usuario, done) {
24 done(null, usuario.usuario);
25 }
26
27 function deserializarUsuario(campoUsuario, done) {
28 encontrarUsuario(campoUsuario, function(err, usuario){
29 done(err, usuario);
30 });
31 }
32
33 var estrategia = new EstrategiaLocal(function(campoUsuario, campoPassword\
34 , done) {
35 encontrarUsuario(campoUsuario,function(error,usuario){
36 if (error) { return done(error); }
37 if (!usuario || usuario.password !== campoPassword) {
38 return done(null, false, { message: 'Usuario y/o contraseña incorre\
39 ctos'});
40 }
41 return done(null, usuario);
42 });
43 });
44
45 return {
46 estrategia : estrategia,
47 encontrarUsuario : encontrarUsuario,
48 aseguraLogin : aseguraLogin,
49 serializarUsuario : serializarUsuario,
50 deserializarUsuario : deserializarUsuario
Gestión de Login con Passport 123

51 }
52 };

Lo primero que hacemos es traernos el objeto Strategy del módulo passport-local que instalamos.
La estructura que vemos ahora es la típica para exportar varias funciones. Como ves, necesitamos
los usuarios, que pasaremos como parámetros.
La función encontrarUsuario se encarga de recorrer la lista de usuarios y comparar el campo
usuario de cada objeto y, si lo encuentra, ejecuta la función de callback que recibe como segundo
parámetro. En caso de no encontrarlo, pasamos el valor null para que sepamos que no hemos llegado
a encontrarlo.
Si has estado prestando atención, sabrás que aseguraLogin es un Middleware. Este Middleware lo
usaremos para proteger las rutas que queramos que estén protegidas por el inicio de sesión. Passport
nos facilita el método isAuthenticated que nos devuelve true si el usuario tiene sesión iniciada y
false en caso contrario. Si ha iniciado sesión, llamamos a next para que la ruta continúe su curso.
En caso contrario, guardamos la ruta a la que quería ir al usuario en la sesión en el valor rutaLogin
y lo redirigimos a /login para que inicie sesión.

Sobre esta técnica


Cuando un usuario visita una página que está protegida por contraseña, lo que espera es
que al logarse, pueda volver automáticamente a esta página. Esto es especialmente útil
cuando dejas de usar la aplicación por un tiempo que provoque que la sesión caduque
y, al volver, se pierda. No obstante, es una característica fácilmente eliminable. ¡Intenta
mejorar la experiencia de tus usuarios!

La función serializarUsuario es una función de Passport que es necesaria en caso de valernos


de sesiones (como será nuestro caso). Básicamente es la función que usa Passport para serializar al
usuario cuando el inicio de sesión tiene éxito. En nuestro caso, nos valemos del campo usuario para
que los datos guardados en sesión sean los mínimos posible.
Por el contrario, la función deserializarUsuario se encarga del proceso contrario. A raíz del
usuario, nos devuelve el objeto completo del usuario gracias al uso de encontrarUsuario. A raíz de
esta función, Passport es capaz de dejarnos en el objeto req.user, todos los datos del objeto usuario,
es decir su nombre, contraseña y usuario en nuestro caso.
Lo último que definimos es la Estrategia. La estrategia recibe el usuario y la contraseña y una función
de callback. Gracias a la función encontrarUsuario, lo buscamos por el campoUsuario. Si hay un
error, se lo pasamos al callback. Si por desgracia, la contraseña no coincide, pasamos al callback
tres parámetros: El primero como siempre indica el error, en este caso al no ser un error de Node,
pasamos null. El segundo indica que la autenticación no ha tenido lugar. El tercero (y opcional),
Gestión de Login con Passport 124

es un objeto con el campo message que se pasará como error de inicio de sesión. Si la contraseña
coincide, pasamos el objeto del usuario al callback.
Finalmente, devolvemos un objeto con todas estas funciones que usaremos en app.js.

/routes/index.js

1 exports.index = function(req, res){


2 res.render('index', { title: 'Express' });
3 };
4
5 exports.getLogin = function(req, res){
6 res.render('login', { mensaje: req.flash('error') });
7 };
8
9 exports.postLogin = function(req, res) {
10 var rutaRedirigir = req.session.rutaLogin || '/';
11 res.redirect(rutaRedirigir);
12 };

Las rutas principales son realmente sencillas.


La primera es la que viene con Express por defecto que no vamos a modificar.
La ruta getLogin se encarga de renderizar la vista login. Además, pasamos como variable mensaje
a la vista, el valor flash de error.

¿Qué es eso de flash?

Si recuerdas, al principio instalamos el modulo connect-flash. Este es usado por Passport para pasar
los mensajes de error. Lo de flash es que son mensajes que se guardan en sesión pero, son de un único
uso. Es decir, una vez obtenidos son flasheados.
La ruta postLogin se encarga de recibir el formulario de login. Ahora mismo estarás pensando, ¿y
no comprobamos el usuario ni nada? No es que sea Mentalista ni nada, como diría Patrick Jane, los
mentalistas no existen. No obstante, es lo que yo pensaría si fueras tú… De toda la parte desagradable
se encarga la estrategia que definimos antes, ¿te acuerdas? Ahora veremos cómo encaja todo este
puzzle.
Gestión de Login con Passport 125

/routes/usuario.js

1 exports.lista = function(req, res, usuarios){


2 res.render('usuarios', { usuarios: usuarios });
3 };

La ruta de usuarios solo nos facilita la lista. En esta nos encargamos de invocar la vista usuarios y
le pasamos nuestra lista de usuarios para mostrarlos en pantalla. Esta ruta es la que vamos a proteger
con contraseña ya que vamos a mostrar todas las contraseñas de los usuarios… ¡y no queremos que
las vean!

app.js

1 var express = require('express'),


2 routes = require('./routes'),
3 user = require('./routes/usuario'),
4 http = require('http'),
5 flash = require('connect-flash'),
6 path = require('path'),
7 passport = require('passport');
8
9 var usuarios = [
10 { nombre : 'Antonio', usuario : 'alaguna', password : '12345' },
11 { nombre : 'Test', usuario : 'test1', password : '12345' },
12 { nombre : 'Test', usuario : 'test2', password : '12345' },
13 { nombre : 'Test', usuario : 'test3', password : '12345' }
14 ];
15
16 var estrategiaLocal = require('./lib/passport/estrategia-local')(usuarios);
17
18 passport.use(estrategiaLocal.estrategia);
19 passport.serializeUser(estrategiaLocal.serializarUsuario);
20 passport.deserializeUser(estrategiaLocal.deserializarUsuario);
21
22 var app = express();
23
24 // all environments
25 app.set('port', process.env.PORT || 3000);
26 app.set('views', __dirname + '/views');
27 app.set('view engine', 'jade');
Gestión de Login con Passport 126

28 app.use(express.favicon());
29 app.use(express.logger('dev'));
30 app.use(express.bodyParser());
31 app.use(express.cookieParser());
32 app.use(express.session({ secret: 'el oso y la doncella' }));
33 app.use(express.methodOverride());
34 app.use(flash());
35 app.use(passport.initialize());
36 app.use(passport.session());
37 app.use(app.router);
38 app.use(express.static(path.join(__dirname, 'public')));
39
40 // development only
41 if ('development' == app.get('env')) {
42 app.use(express.errorHandler());
43 }
44
45 app.get('/', routes.index);
46 app.get('/login', routes.getLogin);
47 app.get('/usuarios', estrategiaLocal.aseguraLogin, function(req,res){
48 user.lista(req,res,usuarios);
49 });
50
51 app.post('/login', passport.authenticate('local', { failureRedirect: '/logi\
52 n', failureFlash: true }), routes.postLogin);
53
54 http.createServer(app).listen(app.get('port'), function(){
55 console.log('Express escuchando por el puerto ' + app.get('port'));
56 });

Como es natural, no vamos a repetir todo lo de Express y nos vamos a centrar únicamente en la
parte de Passport.
Lo primero, como suele ser habitual, es traernos todas las dependencias. Ademas, definimos nuestra
lista de usuarios como hemos visto antes, y nos traemos también nuestra estrategiaLocal a la que
le pasamos la lista de usuarios para que pueda funcionar.
Con passport.use le estamos diciendo a Passport que tiene que usar esa estrategia para autenticar
a los usuarios. Además, le indicamos las funciones serializeUser y deserializeUser.
En nuestra pila de Middlewares de Express hemos colado 3:
Gestión de Login con Passport 127

1 app.use(flash());
2 app.use(passport.initialize());
3 app.use(passport.session());

El primero (opcional) sirve para los mensajes flash. El segundo para que Passport pueda funcionar, y
el tercero (también opcional) permite a Passport el uso de sesiones persistentes. Es muy importante
que passport.session esté después de exporess.session para que todo funcione correctamente.
En nuestras rutas, verás que /usuarios pasa primero nuestra estrategiaLocal.aseguraLogin que
se asegura de que hayamos iniciado sesión en el sistema antes de enviar información sensible.
En la ruta post de /login, como primera función pasamos passport.authenticate('local'). Esto
lo que hace es uso de nuestra estrategia local para iniciar sesión. Pasamos un par de parámetros en
un objeto de configuración: failureRedirect es a dónde será redirigido el usuario en caso de que
la autenticación no tenga éxito. failureFlash se encarga de que haya mensajes flash en la sesión.
Si la autenticación tiene éxito, se llama a la otra ruta que hemos definido: routes.postLogin.

views/login.jade

1 extends layout
2
3 block content
4 if locals.mensaje
5 p.error= mensaje
6
7 form(action="/login", method="post")
8 div
9 label(for="usuario") Usuario
10 input#usuario(type="text",name="username")
11 div
12 label(for="password") Contraseña
13 input#password(type="password",name="password")
14 div
15 input(type="submit") Enviar

Nuestro sencillo formulario de login es una plantilla de Jade que tan solo tiene un formulario. En
caso de que haya una variable mensaje (que viene de la sesion), mostramos un párrafo con el error
de inicio de sesión.
Es “importante” que el usuario tenga como name el valor username y la contraseña password pues
esto es lo que espera Passport y, si no, nos devolverá un error por defecto: Missing credentials
En caso de que quisiéramos cambiar este comportamiento, tenemos que hacerlo en la estrategia:
Gestión de Login con Passport 128

1 new EstrategiaLocal({
2 usernameField: 'usuario',
3 passwordField: 'contraseña'
4 },function(campoUsuario, campoPassword, done) {
5 ...
6 });

De esta forma, podemos definir los campos como queramos.

views/usuarios.jade

1 extends layout
2
3 block content
4 table
5 thead
6 tr
7 th Nombre
8 th Usuario
9 th Password
10 tbody
11 each usuario in usuarios
12 tr
13 td=usuario.nombre
14 td=usuario.usuario
15 td=usuario.password

Esta sencilla vista, itera sobre la lista de usuarios y los muestra en una tabla. ¡Viva la seguridad!
Aunque el ejemplo aquí indicado no sea el escenario más real posible (dado que lo habitual es usar
una base de datos), sirve como base para ilustrar un proceso de login habitual de una aplicación en
Node.js con Express y Jade. Es fácil sentirse abrumado al principio pero, una vez implementado,
proteger una ruta es un proceso realmente sencillo que únicamente necesita añadir un sencillo
Middleware que se encarga de todo.
Gestionando la subida de ficheros
En casi todo momento de la vida de un programador, llega un punto en el que uno tiene que permitir
que suban ficheros a través de la aplicación. Es un momento que todos tememos. Todos sabemos
traer datos de una base de datos, procesarlos, etc. ¿Pero subida de ficheros? Es algo que tratamos
una vez entre cien y, como no tenemos tanta experiencia, se puede llegar a hacer un mundo.
Con Node.js, al principio era un verdadero dolor de muelas. No obstante, como vamos a ver ahora
mismo, con Express la cosa se ha solucionado en gran medida. No necesitamos ningún módulo extra
ni nada por el estilo, Express cuando lo sacamos de su caja puede hacerse cargo de la situación él
solo. Es todo un niño mayor.
Vamos a empezar por la parte fácil. Lo primero que necesitamos es un formulario que nos permita
poner ficheros. Debería ser sencillo… vamos a ver:

1 form(action="/subida", method="post", enctype="multipart/form-data")


2 // Se traduce en
3 // <form action="/subida" method="post" enctype"multipart/form-data"></form\
4 >

Eso es. Puede que lo sepas, o puede que no. Yo la verdad es que tardé en aprender porqué esto era
necesario. Me voy a poner el sombrero de abuelo de HTML y a explicar porqué es esto necesario.
Los formularios HTML ofrecen dos formas de codificación. La primera, y es la que se usa por defecto
es application/x-www-form-urlencoded. Esta no tenemos porqué especificarla nunca. Podríamos
por supuesto pero haría los formularios más horribles de lo que ya son. Básicamente, esta codifica-
ción es similar a la que usamos cuando tenemos algo así: mi_pagina/?parametro1=valor1&parametro2=valor2.
Imagina lo que pasaría si pones un archivo en ese tipo de formato. Directamente petaría. El servidor
no sabría cómo volver a unir los datos y habríamos perdido todo.
Entonces aparece multipart/form-data en escena. Esto indica al protocolo HTTP que vamos a
enviar un archivo y que se prepare para lo que viene. Los ficheros tienen su peculiaridades y de esta
forma, el servidor está preparado para que le mandes ficheros. La única pega (por llamarlo de alguna
manera) es que tenemos que usar el método POST para estos casos. Pero tampoco es una pega real,
¿no?
Ahora que ya tenemos el formulario, tendremos que añadir algo para poder adjuntar el fichero, el
típico botón que dice “Escoger archivo”. No debería costarnos mucho…
Gestionando la subida de ficheros 130

1 form(action="/subida", method="post", enctype="multipart/form-data")


2 input(type="file", name="fichero")
3 input(type='submit', value='Subir')

Perfecto. Me he tomado la libertad de añadir un botón para enviar el formulario. El campo input de
tipo file no tiene ningún misterio. Únicamente tenemos que quedarnos con el nombre ya que será
la forma que tendremos luego de recuperarlo.
Ahora que ya tenemos listo toda la parte que se encarga del envío (que estoy seguro de que te ha
resultado muy complicado), vamos a prepararnos para la recepción. Es igual de complicado. Ya lo
verás.
¿Recuerdas este Middleware?

1 app.use(express.bodyParser());

Esta es la definición que le dí:

Este Middleware es el encargado de procesar el cuerpo de las peticiones que llegan a


nuestra aplicación. Para que nos entendamos, es el encargado de que una petición POST
coloque los parámetros en req.params o nos facilite los archivos de haberlos.

Pues, como podrás imaginar, es el que se encarga de que podamos hacernos con el fichero. Sin él,
nada sería lo mismo. ¡Así que no te olvides de él!
Lo que tenemos que crear es una ruta. Recuerda que en este caso, forzosamente ha de ser del tipo
POST para gestionar el envío del formulario. Lo hemos definido como method pero es que además,
nos viene forzado por el enctype. Vamos a probar a ver qué recibimos:

1 app.post('/subida',function(req,res){
2 console.log(req.files);
3 });

Según Express, los archivos que se han subido quedan “visibles” en el parámetro files de la petición.
Si intentamos hacer una subida, deberíamos ver algo así en la consola:
Gestionando la subida de ficheros 131

1 { fichero:
2 { domain: null,
3 _events: {},
4 _maxListeners: 10,
5 size: 163694,
6 path: 'C:\\Users\\ALaguna\\AppData\\Local\\Temp\\c1351ae1cbc6096a5ae3c\
7 d1614
8 2f9b83',
9 name: 'Captura.PNG',
10 type: 'image/png',
11 hash: null,
12 lastModifiedDate: Mon Jun 10 2013 21:20:46 GMT+0100 (Hora de verano GM\
13 T),
14 _writeStream:
15 { ... },
16 ...
17 }
18 }

Como ves, consiste en un objeto. Si te has fijado bien, el objeto tiene una propiedad con el nombre
del campo que usamos. En nuestro caso, como somos muy originales, le dimos e nombre de fichero.
Además tenemos una serie de datos interesantes:

• size : Este es el tamaño en bytes del archivo. Útil si queremos realizar algún tipo de validación
de tamaño.
• path : Esta es la ruta temporal donde el archivo está almacenado. Como ves, tiene un nombre
raro y está guardado en un directorio bastante absurdo. Ahora veremos cómo podemos
cambiar eso…
• name : Es el nombre original que el usuario le dio al archivo, junto a su extensión.
• type : Es el tipo de archivo que hemos subido. Es otro dato interesante si queremos hacer
validaciones. Imagina que estamos mostrando una galería de fotos. Solo querremos que la
gente suba imágenes, ¿no? Pues este es el parámetro que nos dice qué es el archivo en sí.

El resto de parámetros y funciones no me parecen del todo interesantes ni necesarios en la mayoría


de los casos por lo que los obviaremos por ahora.
Bueno, parece que el archivo está en esa ruta fea. Si nos da por ir a esa ruta, y renombramos el
archivo con la extensión original, veras que se abre y que tiene el mismo contenido. ¿Pero no es un
sitio muy feo para guardar subidas? ¿Por qué se guarda ahí?
En realidad, es la ubicación temporal. Esa es la de Windows. En sistemas Unix el directorio
temporal, como no podría ser de otra manera, es tmp. El caso es que podemos cambiar ese directorio
temporal. Vamos a ello:
Gestionando la subida de ficheros 132

Lo primero que vamos a hacer es crear una carpeta en nuestra aplicación que se llame temp,
idealmente en la raíz de la misma. De esta forma, no queda visible directamente el contenido ni
es servido bajo ningún concepto. Una vez que hayamos creado la carpeta, tenemos que informarle
a Express de que queremos que ese sea el nuevo directorio de subidas. ¿Dónde? Pues en nuestro
amigo, el Middleware:

1 app.use(express.bodyParser({ uploadDir:__dirname + '/temp/' }));

Pasando un objeto de configuración a bodyParser podemos modificar levemente su comportamien-


to. Otra opción que tiene es keepExtensions. Si establecemos esta propiedad a true, el Middleware
se encargará de que la extensión persista, por lo que en vez de tener un archivo temporal que sea
así:

1 c1351ae1cbc6096a5ae3cd16142f9b83

Tendremos otro que será así:

1 c1351ae1cbc6096a5ae3cd16142f9b83.PNG

Mucho más descriptivo, ¡dónde va a parar! [/ironía]


Ahora que ya tenemos el archivo ubicado, y está encima en nuestro sistema, podemos trabajar con
él fácilmente. Así que lo mejor que podemos hacer es moverlo. Sí, moverlo. Pero antes tendremos
que asegurarnos de que cumple con nuestras normas. No voy a escribir una función para ello,
estoy seguro de que podrás hacer un bonito Middleware que se encargue de validarlo, ¿verdad?
¿!VERDAD?!
Bueno, en vez de un Middleware, voy a poner el nombre de una función. Ahora verás porqué:

1 app.post('/subida',function(req,res){
2 var destino = './public/subidas/' + req.files.fichero.name;
3
4 if (archivoValido(req.files.fichero)){
5 fs.rename(req.files.fichero.path, destino, function(err){
6 if (err) { throw err; }
7 res.redirect('/subidas/'+ req.files.fichero.name);
8 });
9 }
10 else {
11 fs.unlink(req.files.fichero.path, function() {
12 if (err) throw err;
13 res.send('El fichero que has enviado no cumple con mis expectativas..\
Gestionando la subida de ficheros 133

14 .');
15 });
16 }
17 });

Veamos. Lo que hacemos es preparar una variable que convenientemente llamamos destino en la
que almacenamos la ruta donde vamos a guardar lo que subamos. En este caso es en la carpeta
subidas que habremos creado dentro de public. De esta manera podemos hacer que sea visible en
nuestro servidor. Además, usamos el nombre original del archivo. Ahora estarás pensando que esto
puede hacer que los nombres de los archivos se repitan. Y es cierto… es por ello que, si realmente
vas en serio con esto, o bien lo compruebas o bien creas nombres de archivos únicos.
Lo siguiente que hacemos es validar el fichero. En caso de que sea válido, confiando en el usuario,
usamos la función rename. El nombre es un poco engañoso ya que puedes pensar que lo que hace es
renombrar el fichero. ¡Para eso podrían haberle puesto centrifugar! Pero no vayas a pensar, querido
lector, que esto está así sin motivo. Puede que ya lo hayas adivinado. Si es así, te regalo un pin. Para
los menos avezados de la clase, ejem, lo que ocurre es que en sistemas Unix, para renombrar un
archivo lo que hacemos es moverlo al mismo sitio… ¡pero con distinto nombre!
Cuando terminamos de mover el archivo, mandamos al usuario a la nueva ruta del archivo para
que pueda verlo. No sé muy bien porqué un usuario querría ver un archivo que hace unos segundos
tenía en su escritorio pero, ¡algo tenía que poner ahí!
En caso de que el archivo no pase la validación nosotros somos los encargados de borrar el fichero
de su ubicación temporal. Para ello usaremos la función unlink que, si alguna vez has usado PHP,
te sonará ya que tiene exactamente el mismo nombre. Lo sé, en estos momentos estarás pensando,
pero si PHP y Node.js son como Manzanas y Coliflores… ¡No se parecen! Pues tienen algo en común:
el lenguaje C. La función unlink es una función de C y, como comentamos al inicio del libro, una
parte nada desdeñable de Node.js es C y PHP, a bajo nivel, es C también. Como dice el dicho, ¡nunca
te acostarás sin saber una cosa más!
Como te habrás fijado, tanto el mover el fichero como el borrarlo, son operaciones asíncronas por
lo que el servidor puede seguir trabajando mientras espera al resultado de la misma. ¡Puro estilo
Node.js!
¿A que ha sido fácil? ¿Sí? Pues vamos a echar un brevísimo vistazo a cómo cambian las cosas si
queremos subir varios archivos a la vez.

Subiendo varios ficheros


Volvamos a nuestro formulario. Estoy seguro de que estás tentado a poner otro campo de tipo file,
¿verdad? ¡No lo hagas!
Gestionando la subida de ficheros 134

1 form(action="/subida", method="post", enctype="multipart/form-data")


2 input(type="file", name="fichero", multiple="multiple")
3 input(type='submit', value='Subir')

Lo único que necesitamos es añadir la propiedad multiple al campo para que acepte múltiples
ficheros. Si recargas el formulario, y abres la página, verás que ahora puedes seleccionar varios
ficheros a la vez manteniendo pulsada la tecla Ctrl/Cmd.
Y bien, ¿ahora qué pasa en el servidor?
Seguro que puedes imaginártelo. El campo req.files.fichero ha dejado de ser un objeto. No te
pongas triste, ahora es algo mejor… ¡Una matriz!

1 [
2 { domain: null,
3 _events: {},
4 _maxListeners: 10,
5 size: 19363,
6 path: 'C:\\xampp\\htdocs\\node-upload\\public\\temp\\a14034886d10
7 4d828617c508f802a.PNG',
8 name: 'Captura2.PNG',
9 type: 'image/png',
10 hash: null,
11 lastModifiedDate: Mon Jun 10 2013 22:33:07 GMT+0100 (Hora de verano GMT\
12 ),
13 _writeStream: [Object] },
14 { domain: null,
15 _events: {},
16 _maxListeners: 10,
17 size: 205450,
18 path: 'C:\\xampp\\htdocs\\node-upload\\public\\\temp\\4a9179a8178b
19 f22e560c2800f0ab0.PNG',
20 name: 'Captura.PNG',
21 type: 'image/png',
22 hash: null,
23 lastModifiedDate: Mon Jun 10 2013 22:33:07 GMT+0100 (Hora de verano GMT\
24 ),
25 _writeStream: [Object] }
26 ]

Únicamente tenemos que modificar nuestra función para ser algo así:
Gestionando la subida de ficheros 135

1 app.post('/subida',function(req,res){
2
3 req.files.fichero.forEach(function(fichero){
4 var destino = './public/subidas/' + fichero.name;
5
6 if (archivoValido(fichero)){
7 fs.rename(fichero.path, destino, function(err){
8 if (err) { throw err; }
9 });
10 }
11 else {
12 fs.unlink(req.files.fichero.path, function() {
13 if (err) throw err;
14 });
15 }
16 });
17
18 res.send('Ficheros subidos correctamente');
19 });

Como ves, lo que hacemos es iterar sobre la matriz de ficheros y repetir la misma operación que
hicimos antes pero esta vez para mover cada fichero. Además, dado que no tiene sentido redirigir al
usuario a cada foto… ¡Le decimos que ya ha terminado todo!
Ánimo, pruébalo en tu servidor y verás cómo los ficheros aparecen en la carpeta de ficheros. ¡No
dirás que te ha costado trabajo ehh! Ahora ya podrás decir que sabes poner un formulario de subida
de ficheros para que la gente suba cosas en tu aplicación.
Resumen
En este capítulo final hemos visto un montón de cosas interesantes. Antes de que te pongas triste
porque el libro se acaba te diré que aun no se acaba. Aun veremos un par de cosas más en Apéndices.
Pero no nos desviemos.
En este capítulo nos hemos metido de lleno con Express. Hemos visto cómo podemos instalarlo
fácilmente y cómo aprovecharnos de la instalación global para que nos genere automáticamente
una estructura de carpeta por nosotros con tan solo un sencillo comando. Además hemos explorado
la estructura de carpetas que nos crea, aprovechando para ver en perspectiva la estructura de carpetas
que vimos en el capítulo anterior.
A continuación, hemos visto cómo podemos configurar Express en profundidad repesando cada una
de las opciones de configuración habidas y por haber. ¡Incluso las que no están en la documentación!
Gracias a la configuración podemos cambiar el comportamiento del framework y, lo que es mejor,
podemos establecer nuestros propios valores de configuración.
Luego, nos hemos sumergido en las rutas con profundidad ya que, en el fondo, son el corazón
de Express. Hemos aprendido cómo usar parámetros, cómo hacer que estos sean opcionales e
incluso expresiones regulares. Finalmente también hemos podido ver las cadenas de búsqueda como
parámetros y cómo poder extraer los datos de la cadena y, además, hemos descubierto cómo podemos
agrupar los parámetros para formar matrices de valores con una misma clave.
Uno de los temas quizá más complejos, ha sido el de los Middlewares. Esas pequeñas (espero)
funciones que se colocan en medio de nuestras rutas para hacer cosas adicionales. Hemos aprendido
a crearlos, la importancia que tiene el orden en ellos y, además, hemos revisado cada uno de los
Middlewares que Express trae por defecto y que podemos utilizar.
Para “cerrar” con Express, hemos echado un vistazo a las opciones y funciones que tanto la respuesta
como la req (petición) nos pueden dar. Por ejemplo ahora sabemos cómo obtener la IP de la petición
o si una petición es AJAX o no. La respuesta quizá haya sido más interesante porque, finalmente,
es lo que devolvemos al usuario así que hemos visto cómo redirigir a otras rutas, aprendido algunas
cosas sobre los códigos HTTP y, por supuesto a renderizar plantillas.
Esto nos dio pie a hablar sobre Jade, el sistema de plantillas por defecto de Express, del cual hemos
hablado largo y tendido. Especialmente su sintaxis es un poco peculiar al principio pero (espero)
hemos aprendido a domarla. Hemos tratado el uso de variables locales, disponibles para toda la
aplicación dentro de las plantillas y además hemos visto cómo evitar que las plantillas exploten por
no tener una variable presente. Hemos descubierto lo útiles que son los bloques de código en Jade los
cuales podemos usar para iterar sobre objetos y/o matrices o modificar dramáticamente la apariencia
en función del valor de una variable.
En la recta final del Capítulo, hemos aprendido algunos trucos para mejorar la experiencia o añadir
alguna funcionalidad a nuestra aplicación. Primero, hemos visto cómo podemos trasladar al usuario a
Resumen 137

páginas personalizadas cuando obtienen un error 404 o 500 gracias al uso de los Middlewares. Luego,
hemos aprendido a añadir un sistema de Login básico con Passport a Express creando una pequeña
aplicación con usuarios (locales) de prueba. Finalmente, hemos tratado cómo realizar subidas de
fichero a nuestro servidor desde un formulario tanto de un solo fichero como de múltiples a la vez
e, incluso, hemos aprendido cómo podemos hacer una validación básica de los ficheros que suben a
nuestro servidor.
Y hasta aquí llega nuestro camino. Como aventuré al principio del Resumen, aun queda camino
por recorrer. Mi intención es la de añadir un par de Apéndices con dos aplicaciones (no demasiado
complejas) para tratar base de datos (probablemente con Mongo) y Socket.io. Ambos apéndices,
quizá sean los más largos por la cantidad de código que contendrán, aunque ya tenemos la mayoría
de los conocimientos para hacer esas aplicaciones!
Como decía Desmond:

Te veré en otra vida, colega


Apéndice A: Ejecuta tu aplicación
Node.js “siempre”

Perfecto. Ya tienes tu aplicación de Node.js. Se la has enseñado a tu jefe y está contento. Ha llegado
la hora de dar el salto, de ponerlo en producción y disfrutar de tu obra maestra. Llegas al servidor,
lo subes y escribes:

1 $ node app.js

Todo el mundo puede acceder. Corre el champán y te sientes en la cumbre. Mañana pedirás un
ascenso, ¡te lo has ganado! Cierras la ventana del terminal. Recoges tus pertenencias y te diriges a la
puerta con una sonrisa triunfal. Escuchas voces que gritan tu nombre. Sigues sonriendo pues estás
en las nubes. De repente alguien no te deja salir. La aplicación ha dejado de funcionar.
Quizá no a un nivel tan épico, (o quizá solo lo fue para mi) pero yo estuve en esa situación.
Obviamente antes de pasarlo a producción. O eso creo. El caso es que en cuanto cierras la ventana
del terminal, todo parece terminar. Lo más habitual es que queramos que esa aplicación se ejecute
por siempre jamás así que empiezas a maldecir al creador de Node.js.
¡No te preocupes! Vamos a ver cómo podemos solucionar esto.

Atención
Lamentablemente este capítulo es únicamente para entornos Unix ya que Windows no
tiene el entorno de “demonios” que se usa en Unix. Hay forma, no obstante, de establecer
un programa Node.js como servicio de Windows aunque, por el momento, queda fuera
del alcance de este apéndice.
Ejecutando Node.js en segundo plano
Si eres un Linuxero bueno, seguro que se te ha ocurrido que podrías ejecutar el servidor con nohup.
Literalmente, nohup significa que el programa ha de ignorar la señal HUP (cuelgue) que es básicamente
cuando una ventana del terminal se cierra.
Entonces escribes un sencillo

1 $ nohup node NODE_ENV=production app.js &

Recuerda
Estamos usando NODE_ENV para definir el entorno en el que vamos a ejecutar nuestra
aplicación. Puedes repasar este tema en el “Capítulo 1 : Accediendo a las variables de
Entorno”

Cierras la ventana. Abres el navegador y… ¡voilá! Funciona. Todo el mundo puede volver a conectar
con tu aplicación y puedes empezar a sonreír. Pero esta vez la alegría dura menos. Un usuario entra
en una ruta extraña y provoca un error que no tenías controlado. El servidor se detiene y otra vez
nadie puede acceder. Tu jefe comienza a dudar de ti.
Necesitas poner algo que se reinicie si peta. Ya tendrás tiempo de arreglarlo.
Usando forever
Forever significa literalmente en inglés siempre. Así que supongo que es un módulo que nos viene
que ni pintados para este caso. ¿Verdad? Siempre significa siempre, más le vale que sea verdad.
Forever es una aplicación de Node.js que es bastante sencilla de usar. Antes de meternos en faena,
vamos a instalarla. Necesita que la instalemos globalmente ya que puede que necesitemos más de
un proceso ejecutándose a la vez y porque, sencillamente, forever es un programa de la línea de
comandos.

1 $ npm install -g forever

Forever se ocupa de que, si la aplicación cae, se vuelva a levantar. El comando para levantar la
aplicación sería:

1 $ NODE_ENV=production forever start app.js

Y ahora, sí que nos podríamos ir a casa porque la aplicación quedará levantada para siempre. Pero
esto no acaba aquí. Sigue leyendo antes de irte porque puede que lo necesites.
Forever habrá puesto nuestra aplicación a funcionar y únicamente dirá algo como:

1 info: Forever processing file: app.js

Si ahora abrimos nuestro navegador e intentamos acceder, verás que, efectivamente, aparecerá todo
perfecto. Si ejecutamos la ruta que hace que la aplicación falle, verás como se vuelve a levantar sola.

Descubre más
Forever está ubicado, como no, en un repositorio de Github en el que, además, podemos
encontrar toda la documentación disponible. No dejes de echarle un vistazo: Repositorio
de Forever⁴⁴

⁴⁴https://github.com/nodejitsu/forever
Usando forever 141

Operaciones con los procesos


Forever nos ofrece una serie de comandos que podemos aprovechar para sacar aun más partido a
la funcionalidad que ofrece. Aunque no vamos a ver todas las opciones, sí que vamos a estudiar
algunas de ellas.

Listando los procesos que se ejecutan


En ocasiones, querrás saber qué procesos son los que se están ejecutando. Puede que tengas uno
solamente pero siempre es bueno saber si se está o no ejecutando ese proceso que, creemos, debería
estar levantado.
Para ello no tenemos más que ejecutar lo siguiente en el terminal:

1 $ forever list

Y forever, obedientemente, nos devolverá una lista de los procesos que hay ejecutándose:

1 info: Forever processes running


2 data: uid command script forever pid logfile \
3 uptime
4 data: [0] Sqj4 /usr/local/bin/node app.js 56565 56566 /Users/antonio.l\
5 aguna/.forever/Sqj4.log 0:0:0:4.252

Lo más importante que sacamos de ahí es esta información:

• Nombre del script que se está ejecutando: app.js


• ID del proceso que se está ejecutando: 56566
• Archivo del log: /Users/antonio.laguna/.forever/Sqj4.log
• Tiempo que lleva la aplicación ejecutándose sin caerse: 0:0:0:4.252

Si esperamos un rato y volvemos a ejecutarlo, verás como el tiempo (crucemos los dedos) aumenta.

¡Paradlo todo!
Seguro que hay veces en las que quieres parar la ejecución de tu programa. Puede ser porque esté
cascando de lo lindo, o bien porque quieras fastidiar a tu jefe. El motivo es irrelevante. Pero no la
forma de hacerlo.
Podemos ejecutar varios comandos:
Usando forever 142

1 $ forever stop app.js

Esto parará la ejecución de la aplicación y forever te dirá los procesos que ha detenido. Por si no
fuera suficiente, tenemos también la opción de pararlo todo. Para ello usaremos la opción stopall

1 $ forever stopall

Y, nuevamente, recibiremos una lista de todos los procesos que ha detenido. Fácil, ¿no?

Reiniciando la aplicación
Ya sabes cuál es la regla de los informáticos. Si algo no funciona, ¿has probado a reiniciar? Pues,
afortunadamente puedes reiniciar la aplicación, como si ocurriera un fallo pero controlado. Lo más
habitual es que hagas esto después de hacer una actualización, ya que necesitas reiniciar el proceso
si actualizas el código de la misma.
Nuevamente, tenemos dos opciones:

1 $ forever restart app.js

Y, nuestra aplicación, reiniciará y tendrá un estado como si acabara de arrancar.

1 $ forever restartall

Al igual que con stop , podemos reiniciar todas las aplicaciones que tengamos en ejecución

Ubicación de los logs


La primera de la que voy a hablar es la de ver el log. Podríamos hacer algo así ciertamente:

1 $ tail -f /Users/antonio.laguna/.forever/Sqj4.log

Pero lo cierto es que el nombre de ese log va cambiando con el tiempo, con los reinicios, con las
mareas, los ciclos lunares, etc. Así que ¿por qué íbamos a necesitar mirar cada vez la lista de procesos
para ver el nombre del log?
Lo mejor es que le digamos a forever dónde queremos que se guarde el log:

1 $ forever start -o miaplicacion.log -e error.miaplicacion.log app.js

De esta manera, nos estamos asegurando de que los mensajes normales, queden guardados en
miaplicacion.log mientras que los de error, en error.miaplicacion.log de esta manera, tendre-
mos los logs separados y no tendremos mucho problema a la hora de localizarlos.
Si por el contrario te gusta el rollo aleatorio, siempre puedes poner en la consola el siguiente
comando:
Usando forever 143

1 $ forever logs

Y te dirá los logs que tiene ahora mismo abiertos.


Escojas el método que escojas, puedes usar tail -f para ver lo que pasa con tu aplicación,
especialmente en el log de errores.

Consejo extra
Lo más habitual es que si estamos usando forever para mantener nuestra aplicación con vida,
queramos que si el equipo se reinicia, la aplicación vuelva a arrancar cuando el sistema, por el
motivo que sea se reinicie también.
Para ello vamos a valernos de crontab para establecer una rutina en nuestro equipo para cuando
este se reinicie.
Lo primero que vamos a hacer es comenzar a editar nuestro crontab. Escribe este comando en la
consola, reemplazando usuario por el usuario que esté ejecutando en la consola:

1 $ crontab -u usuario -e

Si vas a usar un usuario distinto al actual, tendrás que valerte de sudo.


Una vez que estés en el editor, añade la siguiente línea:

1 @reboot /usr/local/bin/forever start /la/ruta/a/tu/app.js

Usando rutas absolutas al ejecutar comandos con cron, nos aseguramos de que funcionará en
cualquier circunstancia ya que nunca sabes en qué ruta se ejecutará cron ni bajo qué usuario.
Una vez guardes el archivo, deberías ver un mensaje en la pantalla que te dijera que cron ha sido
instalado.
Ahora, cuando el equipo se reinicie, crontab ejecutará esa tarea y volverá a iniciarse automática-
mente. ¡Genial! Ahora puedes volver a hablar con tu jefe y pedirle ese aumento de sueldo.
Apéndice B: Creando un chat con
Socket.io

Ahora que ya tenemos algo más de conocimientos, podemos pasar a crear una aplicación más
compleja, que ponga a prueba todas nuestras habilidades y, de paso, porqué no, aprender un poco
más.
En esta aplicación de prueba usaremos Socket.io. He hablado ya sobre Socket.io a lo largo y ancho
de este libro pero, vamos a intentar darle un poco más de protagonismo.

¿Qué es Socket.io?
Socket.io es, de primeras, un módulo de Node.js. Sí, de esos que podemos instalar con un npm
install. El módulo está pensado para facilitar la comunicación en tiempo real. Hoy en día, los
navegadores modernos tienen Websockets que permiten una comunicación constante entre cliente
y servidor. De esta manera, tanto uno como el otro, pueden estar hablando contínuamente y
manteniendo al usuario de la aplicación al día de lo que va ocurriendo. No obstante, hay navegadores
que no soportan Websockets de forma nativa y, para ello, surgió Socket.io.
El módulo se compone de dos partes: Una librería del lado del cliente que se ejecuta en el propio
navegador del usuario, y una librería en el lado del servidor. Vamos a intentar explicar un poco cómo
funciona esto.
Cuando un usuario se conecta al servidor, se produce lo que se conoce como handshaking que,
literalmente significa apretón de manos. Lo que ocurre es que tanto cliente como servidor, identifican
cuál es la mejor forma de comunicarse entre sí. Si tenemos a mano los websockets, todo será mucho
más rápido pero, si no, Socket.io intenta usar una de estas tecnologías:

• flash socket : Es un socket basado en Adobe Flash y es el que utilizan las aplicaciones Flash
para mantener una conexión abierta con el servidor.
• jsonp polling y ajax long-polling : Esta tecnología se basa en el envío de una petición desde
el cliente al servidor. El servidor no responde inmediatamente si no que espera hasta que haya
nueva información disponible. Cuando tenemos nueva información, el servidor la envía. Si se
excede el tiempo de espera o se recibe una respuesta, el cliente volverá a hacer otra petición
al servidor volviendo a iniciar el ciclo.
145

• ajax polling : En vez de mantener la petición abierta, el cliente hace una petición cada 5
segundos al servidor (normalmente).

El recurso de estas tecnologías es para navegadores antiguos, es decir, Internet Explorer desde antes
de la versión 10.
Ahora que ya sabemos lo que es, vamos a meternos un poco en faena.

La aplicación que vamos a crear


Dado que Socket.io es una tecnología para implementar comunicaciones en tiempo real, el ejemplo
que se viene usando siempre para estos casos, no es otro que un chat.
No es que no haya otros casos de usos, es que es el más sencillo de explicar y al que más utilidad se
le ve rápidamente, sin necesidad de tener otros sistemas adicionales.
Hasta ahora he usado Socket.io en dos proyectos más:

• Gestión de centralita Asterisk en tiempo real con duraciones de llamadas, agentes que logan
y deslogan en sus teléfonos, etc.
• Visualizador de logs multiservidor en el navegador.

No obstante, he aquí una pequeña captura que muestra la aplicación en acción:

Nuestra aplicación del Chat

La idea es:
146

• Mostrar usuarios conectados


• No permitir que un usuario tenga el mismo nombre que otro
• Cuando un usuario se desconecte, debe desaparecer directamente del resto de usuarios
conectados al chat.
• Cuando un usuario nuevo se conecta, tras indicar su nombre de usuario, recibirá en pantalla
los últimos mensajes y los usuarios conectados.
• Los mensajes han de estar escapados para evitar inyecciones de código en la página.

He colgado además el código en Github, por lo que podéis verlo… ¡e incluso mejorarlo!
https://github.com/Belelros/socketchat-es

Instalando las dependencias


Nuestras dependencias son las siguientes:

1 "express": "3.2.4",
2 "jade": "*",
3 "socket.io": "~0.9.16"

No obstante, ya que a estas alturas deberías tener instalado Express, vamos a aprovecharnos de su
estructura y de su generador:

1 $ express socketchat

Ahora que ya tenemos nuestra estructura de carpetas creada (qué flojos somos) vamos a instalar los
paquetes que Express ha puesto en package.json:

1 $ npm install

Finalmente, vamos a instalar socket.io con la siguiente opción:

1 $ npm install socket.io --save

¡Bien! Ahora ya tenemos todas las herramientas necesarias para poder crear nuestra aplicación.
Primero vamos a ver el código de Node de la aplicación. Te sorprenderá lo corto que es.

El lado servidor
147

app.js
1 var express = require('express')
2 , routes = require('./routes')
3 , http = require('http')
4 , path = require('path')
5 , config = require('./config')
6 , socketio = require('socket.io')
7 , chat = require('./lib/chat');
8
9 var app = express(),
10 server = app.listen(config.port || 3000),
11 io = socketio.listen(server),
12 usuarios = [], mensajes = [];
13
14 process.setMaxListeners(0);
15
16 io.configure(function(){
17 io.enable('browser client minification');
18 io.enable('browser client etag');
19 io.enable('browser client gzip');
20 io.set('log level', 1);
21 io.set('transports', [
22 'websocket', 'flashsocket', 'htmlfile', 'xhr-polling', 'jsonp-polling'
23 ]);
24 });
25
26 // all environments
27 app.set('views', __dirname + '/views');
28 app.set('view engine', 'jade');
29 app.use(express.favicon());
30 app.use(express.logger('dev'));
31 app.use(express.bodyParser());
32 app.use(express.methodOverride());
33 app.use(app.router);
34 app.use(express.static(path.join(__dirname, 'public')));
35
36 // development only
37 if ('development' == app.get('env')) {
38 app.use(express.errorHandler());
39 }
40
148

41 app.get('/', routes.index);
42
43
44 io.sockets.on('connection', function(socket){
45 socket.on('conexion',function(datos){
46 chat.conexion(socket,usuarios, mensajes,datos)
47 });
48
49 socket.on('mensaje', function(datos){
50 socket.get('usuario', function(err, usuario){
51 chat.conexion(socket, mensajes, usuario,datos)
52 });
53 });
54
55 socket.on('disconnect', function(){
56 socket.get('usuario', function(err,usuario){
57 chat.desconexion(socket,usuarios,usuario);
58 })
59 });
60 });

Como podrás comprobar, estamos aprovechando la estructura por defecto de Express a la hora de
crear una aplicación.
En las dependencias, hemos incluído dos:

1 * `socketio` : Que no es otra cosa que… bueno. No lo explico. Que ya le hem\


2 os dedicado parte.
3 * `chat` : Que contiene lógica del chat para separar.

Tras las dependencias, creamos nuestra variable app, que es una instancia de Expres. Por separado,
creamos un servidor que escucha por un puerto, que por defecto es el 3000.
A continuación, lo que hacemos es añadir la parte de socket.io. Creamos una variable de nombre
io (por convención) y le decimos que escuche al servidor de Express que hemos creado ahora mismo.
¡En solo dos líneas de código!
Luego creamos dos variables, usuarios y mensajes que contendran la lista de usuarios y mensajes
que vayan fluyendo por el chat. De momento vacías.
Antes de configurar Express, nos disponemos a configurar Socket.io. Estas son las opciones que
pasamos:

• Minificación, compresión y caché de la librería en el cliente (esas son las 3 primeras). Durante
el desarrollo las puedes eliminar si quieres bichear en el código en caso de que obtengas fallos.
149

• El nivel de log al mínimo. Si no ponemos eso, aparecerán multitud de mensajes de los sockets
que se van conectando.
• Por último, especificamos, en orden de preferencia, los Sockets que queremos. Están ordenados
de más rápidos a más lentos.

La parte intermedia del resto del archivo, es código normal que debería sonaros a estas alturas. Si
no es así… ¡volved al principio!
Ahora vamos a la parte que más nos interesa. Socket.io nos expone un objeto sockets que no es otra
cosa que un EventEmitter.

Recuerda
Hablamos sobre EventEmitter en un capítulo anterior y quizá sea buena idea que
refresques los conocimientos. ¡No dejes de releer el capítulo!

Por regla general, nos interesa estar pendientes del evento connection que es cuando alguien abre
un canal de comunicaciones desde un navegador, contra nuestro servidor. En ese momento, dentro
del callback, recibimos el objeto socket de la persona que se acaba de conectar que a su vez es un
EventEmitter también.
Ahora nos quedamos escuchando a 3 eventos que puedan emitir estos sockets:

• conexion : Este es el evento en el que se conecta alguien y nos da un nombre de usuario. Esto
hará que aparezca en la lista de usuarios conectados y generará un usuario único en caso de
que haya más de uno con el mismo nombre.
• mensaje : Este es el evento que ocurre cuando un usuario envía un mensaje al chat. La función
solo se ejecuta cuando obtenemos el valor del usuario del Socket, que es una operación
asíncrona. En seguida veremos más sobre esto.
• disconnect : Este es el evento que ocurre cuando un usuario abandona nuestra aplicación.
Aquí querremos avisar a la gente de que esta persona se ha ido, manteniendo la lista de
usuarios conectados actualizada.

Entiendo que esto es un poco confuso ahora mismo. Piensa en toda esta última parte como en el
primer día en una empresa de seguridad.
Tu jefe, el primer día, te dirá algo así:

• Si alguien entra en esta habitación sin autorización, pides refuerzos y lo arrestas.


• Si alguien atraca a alguien, acude cuanto antes para intentar arrestarlo.
• Cada día al entrar has de dejar constancia de cuando te vas y cuando entras.
150

Estas “órdenes” son comunes a todos los guardias de seguridad. Y las órdenes se les indican al entrar
a trabajar. Nuestros sockets son como esos guardias, nada más entrar les tenemos que decir lo que
tienen que hacer en función de lo que vaya ocurriendo en el sistema.
Vamos ahora con las funciones del chat:

lib/chat.js
1 var helperUsuarios = require('../helpers/usuarios')
2
3 exports.conexion = function(socket, usuarios, mensajes, datos){
4 var usuario = helperUsuarios.usuarioUnico(usuarios,datos.usuario),
5 temp = [];
6
7 socket.set('usuario', usuario);
8 temp.push(usuario);
9 usuarios.push(usuario);
10
11 socket.broadcast.emit('conexion',temp);
12 socket.broadcast.emit('mensaje',[{
13 'tipo' : 'sistema',
14 'usuario' : null,
15 'mensaje' : usuario + ' se ha unido al chat'
16 }]);
17 socket.emit('conexion',usuarios);
18 socket.emit('mensaje',mensajes);
19 };
20 exports.mensaje = function(socket, mensajes, usuario, datos){
21 var mensaje = {
22 'tipo' : 'normal',
23 'usuario' : usuario,
24 'mensaje' : datos.mensaje
25 };
26
27 io.sockets.emit('mensaje', [mensaje]);
28 mensajes.push(mensaje);
29 };
30 exports.desconexion = function(socket,usuarios,usuario){
31 usuarios.splice(usuarios.indexOf(usuario),1);
32 socket.broadcast.emit('desconexion',{ usuario: usuario });
33 socket.broadcast.emit('mensaje',[{
34 'tipo' : 'sistema',
35 'usuario' : null,
151

36 'mensaje' : usuario + ' ha abandonado el chat'


37 }]);
38 }

Estas son simplemente las funciones que se encargan de gestionar los 3 eventos que acabamos de
ver.

Conexión
En la primera, cuando un usuario se conecta, ejecutamos una función que recibe los siguientes
parámetros:

• socket del usuario que se acaba de conectar.


• usuarios que ya están conectados dado que no queremos repetir usuarios.
• mensajes que han tenido lugar, para que el usuario que se acaba de conectar, los pueda leer.
• datos que se han enviado desde el navegador, únicamente el usuario en este caso.

Lo que hacemos es obtener un usuario único con la función usuarioUnico que está en otro fichero:

helpers/usuarios.js
1 exports.usuarioUnico = function(usuarios, usuario){
2 var aux = usuario, numero = 1;
3
4 while (usuarios.indexOf(aux) !== -1){
5 aux = [usuario,numero++].join('_');
6 }
7
8 return aux;
9 };

Como ves, no es nada raro, ni excepcional. Simplemente vamos encadenando números al final,
hasta que alguno está disponible. Por ejemplo, si “Usuario” ya está ocupado, nos dará “Usuario1”,
luego “Usuario2” y así sucesivamente. ¿A alguien le suena el IRC? ¿No? ¿Me estoy haciendo mayor?
Sigamos…
Con la función set, guardamos un valor en el socket. Dado que es un dato que vamos a usar
a menudo, tiene sentido hacerlo así. Aunque es una operación asíncrona, nos da igual cuándo
termine.
En una matriz temporal, guardamos el usuario que acaba de conectarse. Luego entenderemos porqué.
Ahora es cuando ocurre la magia. Vamos línea a línea que creo que merece la pena:
152

1 socket.broadcast.emit('conexion',temp);

Esta línea lo que hace es emitir a todos los sockets, menos al que se acaba de conectar, el valor de
temp, que no es otro que una matriz que contiene el usuario que se acaba de conectar.

1 socket.broadcast.emit('mensaje',[{
2 'tipo' : 'sistema',
3 'usuario' : null,
4 'mensaje' : usuario + ' se ha unido al chat'
5 }]);

Esta línea lo que hace es emitir a todos los sockets, menos al que se acaba de conectar, un mensaje
del tipo sistema, indicando que el usuario se ha conectado al chat.

1 socket.emit('conexion',usuarios);
2 socket.emit('mensaje',mensajes);

Estas dos líneas lo que hacen es enviar al socket que se acaba de conectar, los usuarios actuales y los
mensajes.
Como ves, funciones que son bastante similares, hacen cosas totalmente distintas. Entiendo que es
un poco lioso pero, a la larga, le acabas cogiendo el truco. Para que no te pierdas, aquí pongo una
pequeña lista que puedes imprimir en tamaño A1 y colgarla en la pared… así no se te olvidará.

• Enviar al socket actual en solitario: socket.emit('mensaje', 'prueba');


• Enviar a todos los clientes, incluyendo al que emite: io.sockets.emit('mensaje','prueba');
• Enviar a todos los clientes, menos al que emite: socket.broadcast.emit('mensaje','prueba');
• Enviar a un socket en particular: io.sockets.socket('id_del_socket').emit('mensaje','prueba');

Entiendo que ahora mismo las cosas pueden no tener mucho sentido, pero has de tener en cuenta,
que esto consta de dos partes. El servidor se encarga de enviar y luego todo empieza a cobrar sentido
en el cliente, que veremos en seguida.

Mensaje
La función del mensaje recibe como parámetro el socket que ha enviado el mensaje, la lista de
mensajes, el usuario que ha enviado el mensaje (obtenido del socket con la función socket.get('usuario'))
y los datos que no es otra cosa que un objeto que contiene un parámetro mensaje que tiene el mensaje
que acaba de ser enviado.
La función se encarga de enviar el mensaje a todo el mundo, añadiendo el tipo de mensaje y el
usuario que lo ha enviado y lo guarda en la lista de mensajes.
153

Desconexión
exports.desconexion = function(socket,usuarios,usuario){ usuarios.splice(usuarios.indexOf(usuario),1);
socket.broadcast.emit(‘desconexion’,{ usuario: usuario }); socket.broadcast.emit(‘mensaje’,[{ ‘tipo’ :
‘sistema’, ‘usuario’ : null, ‘mensaje’ : usuario + ‘ ha abandonado el chat’ }]); }
La desconexión es nuestra última función y recibe el socket traidor que está abandonando nuestra
aplicación, la lista de usuarios actual, y el usuario traidor que ha decidido abandonar.
Esta función se encarga de avisar a todo el mundo de que alguien ha abandonado el chat y de enviar
un mensaje del sistema indicando que esta persona se ha marchado.

El lado del cliente


Del lado del cliente solo tenemos un único archivo, que se encarga de gestionar todo. Te aconsejo
que mientras lees, querido lector, saltes entre los apartados del cliente y el servidor, para ver cómo
engancha todo. ¡No te pierdas!
Antes de empezar con el código JavaScript, vamos a ver la plantilla de Jade, que como verás, es
realmente sencilla:

views/index.jade

1 extends layout
2
3 block content
4 #mensajes
5 #lista-mensajes
6 .controles
7 textarea#mensaje
8 input#enviar(type="button", value="Enviar")
9 #conectados
10 ul
11 script(src='/socket.io/socket.io.js')
12 script(src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js")
13 script(src="javascripts/chat.js")

Como ves, son un par de div, una para almacenar los mensajes y otra para los usuarios. Dentro tienen
algunos controles para que el chat pueda cobrar vida como un textarea en la que escribiremos los
mensajes, nuestro botón de enviar, etc.
Lo único que quizá te sorprenda es el archivo de socket.io. Si miras tus carpetas, verás que no tienes
ninguna carpeta que se llame así. No obstante, Express y Socket.io son viejos amigos, y gracias a la
154

configuración que hemos hecho antes, Express será capaz de servir el archivo de Socket.io necesario
para haya comunicación con el servidor.
Ahora sí, ¿estás preparado? ¿De verdad? Pues sigue leyendo, vamos a ver nuestro archivo que
contiene toda la lógica del chat.

public/javascripts/chat.js
1 (function($){
2 var usuario = prompt('Nombre de usuario','Usuario');
3 var Chat = {
4 socket : null,
5 el : {
6 listaUsuarios : $('#conectados ul'),
7 listaMensajes : $('#lista-mensajes'),
8 textoMensaje : $('#mensaje'),
9 botonEnviar : $('#enviar')
10 },
11 iniciar : function(usuario){
12 this.conectarSocket();
13 // Enviando el usuario al servidor
14 this.socket.emit('conexion', { usuario: usuario });
15 this.asociarEventos();
16 },
17 conectarSocket : function(){
18 this.socket = io.connect('http://localhost:3000');
19 },
20 asociarEventos : function(){
21 this.socket.on('conexion', $.proxy(this.anadirUsuarioAChat, this));
22 this.socket.on('desconexion', $.proxy(this.eliminarUsuarioDeChat, thi\
23 s));
24 this.socket.on('mensaje', $.proxy(this.anadirMensaje, this));
25 this.el.botonEnviar.on('click', $.proxy(this.enviarMensaje, this))
26 },
27 anadirUsuarioAChat : function(datos){
28 var html = '';
29
30 $.each(datos,function(i,usuario){
31 html += '<li>' + usuario + '</li>';
32 });
33
34 this.el.listaUsuarios.append(html);
35 },
155

36 eliminarUsuarioDeChat : function(datos){
37 this.el.listaUsuarios.find('li').filter(function(){
38 return datos.usuario === $(this).text()
39 }).remove();
40 },
41 anadirMensaje : function(mensajes){
42 var html = '';
43
44 $.each(mensajes, function(i, mensaje){
45 var clase = mensaje.tipo ? ' class="'+ mensaje.tipo +'"' : '';
46 html += '<p'+clase+'>';
47 if (mensaje.usuario) {
48 html += '<strong>' + mensaje.usuario + '</strong>: ';
49 }
50 html += mensaje.mensaje;
51 });
52
53 this.el.listaMensajes.append(html);
54 },
55 enviarMensaje : function(e){
56 e.preventDefault();
57 this.socket.emit('mensaje', {
58 mensaje : this.escapar(this.el.textoMensaje.val())
59 });
60 this.el.textoMensaje.val('');
61 },
62 escapar : function(texto){
63 return String(texto)
64 .replace(/&(?!\w+;)/g, '&amp;')
65 .replace(/</g, '&lt;')
66 .replace(/>/g, '&gt;')
67 .replace(/"/g, '&quot;');
68 }
69 };
70
71
72 if (usuario){
73 Chat.iniciar(usuario);
74 }
75 else {
76 alert('Sin usuario no tienes acceso al chat!');
77 }
156

78 })(jQuery);

Vamos poco a poco… que no tenemos prisa alguna.


Lo primero que hacemos es, nada más cargar la página, pedir el usuario con la función prompt
de JavaScript. Por defecto, el valor será Usuario pero el usuario (valga la redundancia) es libre de
cambiarlo.
Saltémonos por un segundo todo lo de var Chat… y llega al final del todo. En caso de que tengamos
un usuario, el Chat se inicia y, en caso contrario, damos una alerta diciendo que el chat no funciona
sin usuario. Fácil, ¿no?

Más información
Si te has fijado, todo el código está envuelto en una función anónima auto-invocada de
JavaScript. Este es un patrón muy conocido pero, si por algún motivo, te resulta ajeno, te
recomiendo que te leas un artículo que escribí⁴⁵ hace tiempo en mi blog, hablando sobre
el tema.

Ahora volvamos sobre el Chat. El Chat no es otra cosa que un objeto de JavaScript con propiedades
y métodos.
Dentro de el, guardamos todos los elementos del DOM que hemos creado en nuestra plantilla. Esto
está considerado una buena práctica ya que solo buscamos en las tripas del DOM una vez… ¡al
principio!
La función iniciar es la que vimos antes en nuestro if abajo del todo. Esta es la que se encarga de
que todo empiece a funcionar. Los nombres de las funciones son bastante aclaratorios, pero vamos
a verlos. Lo primero que hacemos es conectar al socket. Básicamente es esta sentencia:

1 this.socket = io.connect('http://localhost:3000');

En este momento es en el que, en el servidor, se ejecuta la función que le pasamos al método


on('connect') y, a partir de ahí, el servidor está preparado para escuchar los mensajes que vengan
del socket.
Lo siguiente que hacemos es emitir un evento conexion con el usuario que nuestro visitante ha
elegido. En este punto, el servidor intentará decidir si puede quedarse con él o necesita generarle
alguno nuevo.
La siguiente función es asociarEventos que… no te lo vas a creer… ¡asocia eventos a funciones! Solo
nos interesan 4 cosas:
⁴⁵http://www.funcion13.com/2012/10/21/funcion-anonima-auto-invocada-en-javascript/
157

• conexion : Este evento es el que ocurre desde el servidor cuando alguien se conecta.
• desconexion : Este evento es el que ocurre desde el servidor cuando alguien se desconecta.
• mensaje : Este evento es el que ocurre desde el servidor cuando alguien envía un mensaje.
• click : Cuando alguien hace click en el botón de enviar, significa que nuestra aplicación tiene
que enviar el mensaje que hay en textoMensaje al servidor y de ahí, se propagará al resto.

Todas estas funciones usan la función de jQuery $.proxy que no es otra cosa que una función que
toma una como parámetro, y devuelve otra que se ejecutará en el contexto especificado.

Eso queda muy bonito pero, ¿qué quiere decir?

Bueno, si habéis jugado con JavaScript, sabréis que el elemento this tiende a perderse cada poco.
Por ejemplo, en la función del botón de click, si no usáramos $.proxy, el valor de this sería el del
botón. Eso resulta realmente molesto así que de esta forma lo solucionamos de manera que this
siempre será nuestro objeto Chat.

Más información
Una vez más, este es uno de los temas peliagudos de JavaScript que necesitan un
momento de comprensión. Hace tiempo escribí un artículo que escribí⁴⁶ en el blog que
trataba este y otros temas de JavaScript. ¡Te recomiendo una lectura!

Ahora vamos a ver las funciones que son las gestoras de los eventos a los que estamos escuchando.
anadirUsuariosAChat recibe una matriz. Es por eso que en el servidor añadíamos un único usuario
a una matriz temporalmente ya que de esta forma, podemos usar esta función para añadir tantos
usuarios como queramos. Gracias a la función each de jQuery, vamos iterando por la matriz para
crear nuestra cadena HTML que añadiremos a la lista de usuarios.
La función opuesta, eliminarUsuarioDeChat, se encarga de recorrer la lista de usuarios conectados
y filtrarlos. Si el usuario coincide con el que está dentro de datos.usuario, eliminará el nodo de la
lista y desaparecerá.
anadirMensaje recibe al igual que la de los usuarios, una matriz de mensajes para poder reutilizarla.
El funcionamiento es similar al de los usuarios pero es un poco diferente ya que creamos un párrafo
y le añadimos una clase en función del tipo. Si viene el usuario, pues lo añadimos al principio del
mensaje para que sepamos quién lo ha escrito. Puede que no lo recuerdes, pero los mensajes del tipo
sistema, no tienen usuario alguno ya que son como notificaciones del chat al resto de la gente. Por
ello cuentan con un estilo especial.
⁴⁶http://www.funcion13.com/2012/03/16/comprendiendo-las-variables-objetos-funciones-alcance-y-prototype-en-javascript/
158

La función enviarMensaje es la que se encarga de gestionar el click sobre el botón enviar, lo primero
que hacemos es prevenir el comportamiento por defecto del botón con e.preventDefault para evitar
que intente enviar un formulario que no exista.
Gracias a this.socket.emit emitimos un mensaje al que le indicamos únicamente el parámetro
mensaje después de haberlo escapado.

Y como ves, eso es todo. Gracias a la separación de conceptos, minimizamos enormemente la canti-
dad de funciones y código que tenemos que escribir, tanto anadirMensaje como anadirUsuariosAChat
son funciones realmente genéricas y que se usan tanto en la conexión inicial para añadir todos los
mensajes y usuarios, como cuando ocurre de forma individual.
Ahora te toca a ti, destripar la aplicación, instalarla ¡e intentar mejorarla!

También podría gustarte