Sie sind auf Seite 1von 172

Descubriendo Node.

js y Express
Aprende a desarrollar en Node.js y descubre cmo
aprovechar Express
Antonio Laguna
Este libro est a la venta en http://leanpub.com/descubriendo-nodejs-express
Esta versin 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
Tambin por Antonio Laguna
Laravel: Code Happy (ES)
ndice general
Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Dedicado a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Errare humanum est . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Libro en desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Evolucin de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Algunas presunciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Audiencia del libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi
Introduccin a Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Node.js basado en eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
La asincrona por naturaleza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Instalando Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Instalando en Windows y Mac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Instalando en Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Funciona mejor Node.js en algn 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 parmetros a Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
NDICE GENERAL
NPM - Node Packaged Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Bsqueda de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Obtener informacin de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Lista de paquetes instalados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Instalacin de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Desinstalacin de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Paquetes tiles y habituales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Dudas frecuentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Cmo mantener Node.js actualizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Sobre las versiones de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Nuestra primera aplicacin de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Adentrndonos en Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Gestin de dependencias con package.json . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Versionado semntico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 parmetros a require . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Organizando el cdigo de nuestra aplicacin . . . . . . . . . . . . . . . . . . . . . . . . . 49
El archivo de configuracin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Emisin de eventos con EventEmitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Patrn del observador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Emitiendo eventos con Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Pasando parmetros 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 funcin pipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Lectura - Readable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
NDICE GENERAL
Escritura - writable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Lectura y Escritura - Duplex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Transformacin - 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
Introduccin a Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Otros frameworks de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Meteor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Derby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
flatiron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
TowerJS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Por qu Express? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Instalacin de Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Creando la estructura bsica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Welcome to Express - Bienvenido a Express . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Configuracin de la aplicacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Guardando y obteniendo valores en la aplicacin . . . . . . . . . . . . . . . . . . . . . . . 83
Configurando la aplicacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Rutas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Parmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Cadena de bsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Middlewares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
app.use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
En lnea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Mapeado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
En resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Middlewares ofrecidos por Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
La peticin - 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 bsica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Anidando elementos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Variables en Jade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Bloques de cdigo auxiliares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Pginas de error en Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Gestin de Login con Passport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Gestionando la subida de ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Subiendo varios ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Apndice A: Ejecuta tu aplicacin Node.js siempre . . . . . . . . . . . . . . . 138
Ejecutando Node.js en segundo plano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Usando forever . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Operaciones con los procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Consejo extra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Apndice B: Creando un chat con Socket.io . . . . . . . . . . . . . . . . . . . . . 144
Qu es Socket.io? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
La aplicacin que vamos a crear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Instalando las dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
El lado servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
El lado del cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Introduccin
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 habra tenido lugar. Adems, 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 programacin en mi y ese
gusanillo de pegarte horas delante del monitor, apasionado por la programacin.
Por supuesto a Ryan Lienhart Dahl, creador de Node.js que ha hecho que toda esta revolucin sea
posible.
A David Lpez, por su inestimable ayuda ofreciendo un valiossimo feedback con cada captulo/sec-
cin/coma/espacio/trozo de cdigo/etc que he ido publicando. Gracias!
A la comunidad, Node.js no sera 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 ortografa y gramtica. 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 cdigo, te agradecera
que me avisaras envindome un correo a a.laguna@funcion13.com incluyendo el captulo o frase
relevante.
Feedback
Tienes alguna crtica constructiva? Quieres hacerme algn comentario? Crees que alguna seccin
podra ser mejorada? Me quieres enviar un jamn? Para todo eso, y mucho ms, envame un correo
a a.laguna@funcion13.com.
mailto:a.laguna@funcion13.com
mailto:a.laguna@funcion13.com
Introduccin 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 gustara nutrirme del feedback que vayis pudiendo ofrecerme sobre el
libro conforme se va escribiendo y as poder ofrecer un libro que sea lo ms 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 adquisicin. Ten en cuenta que una vez te hagas con el libro, podrs
hacerte con todas las actualizaciones que este tenga, incluso me encargar de que te llegue un correo
avisndote del hito.
Si te has aventurado a hacerte con el libro durante su gestacin, gracias. De verdad. Ponte en
contacto conmigo y hazme saber qu te parece. Como agradecimiento te colocar en los crditos!
Actualmente esto es lo que est en desarrollo:
Apndice A - Uso de Socket.io y aplicacin bsica
Apndice B - Uso de conexin contra BD Mongo? MySQL? Ambos?
mailto:a.laguna@funcion13.com
Introduccin
Esto que lees, son las primeras palabras de mi primer libro, un gran hito! Le he dado bastantes
vueltas a esto de la introduccin as que espero que no quede tan mal despus de todo.
Quiz te ests preguntando, y quin es este para ensearme 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 ledo alguno de los tutoriales de
programacin que escrib en Funcin 13, mi web. O quiz, te hayas cruzado con alguna de mis guas
de World of Warcraft en una pgina de cuyo nombre no quiero acordarme. En cualquier caso me
apasiona ensear, y me apasiona el desarrollo, principalmente el desarrollo web. Es por ello que
te advierto que no encontrars 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 saltrtelo si no te interesa lo entendera
Aun sigues aqu? Entonces es que quieres leer algo sobre mi. Intentar mantenerlo breve. Soy
Antonio Laguna y soy programador web. Original de un pequeo pueblo del Aljarafe sevillano,
en Espaa. Desde pequeo tuve la suerte de trastear con los ordenadores. Con mi padre como
programador desde que la informtica se inici, he tenido siempre a mano algn 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 pginas 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 da a da. Mi trabajo no es disear las webs, es hacer que stas cobren vida, gracias
a JavaScript principalmente.
Fue hace unos aos ya que empec a picarme con Node.js. Tena una necesidad que cubrir en mi
anterior empresa, Accenture y todo lo que lea apuntaba a Node.js. Viniendo de PHP y con algunos
conocimientos bsicos sobre JavaScript, me amarr a esa frase del quin 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 revolucin. JavaScript en el lado del servidor? Quin iba a pensarlo? JavaScript
lleva mucho tiempo entre nosotros y ha evolucionado muchsimo como lenguaje y, por suerte, sigue
hacindolo.
Vale pero, qu es Node.js?
Node.js es un sistema del lado del servidor, para escribir cdigo 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 rpido.
http://www.funcion13.com
Introduccin iv
Segn la pgina de Node.js, esta es la definicin:
Node.js es una plataforma creada sobre el motor JavaScript de Chrome para crear
aplicaciones de red rpidas 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? Adems, puede que te interese saber que compaas como Microsoft, Linkedin o
Yahoo ya estn usando Node.js para el software.
Un servidor?
S, has ledo bien. Node.js es capaz de crear un servidor web y no tendrs 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 muchsima
libertad a la hora de escribir cdigo y sientes que todo est en tus manos.
Para que te hagas una idea, crear un servidor ocupa tan solo seis lneas de cdigo 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 cdigo bastante sencillo de
entender.
Una de las mejores cosas que tiene Node.js son las libreras 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/
Introduccin v
Forever - Te permite ejecutar una aplicacin Node.js para siempre, en caso de que algn 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 virgueras
con l como responder a distintos verbos HTTP, ejecutar cdigo antes y/o despus de cada
ruta, etc.
Socket.io - Comunicacin en tiempo real con el navegador a travs de WebSockets si estn
disponible, o algunas soluciones menos rpidas en caso de ser otro navegador (Internet
Explorer).
Jade - Un sistema de plantillas usado por Express que nos evita tener que escribir mucho
cdigo para crear HTML.
Evolucin de Node.js
Node.js ha ido cambiando muchsimo desde que apareci al pblico general. Poco a poco ha ido
revolucionando la tecnologa y ms y ms empresas se animan a usar Node. Lo bueno, es que gracias
a su versatilidad, puede que ests 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 estn un
poco ms calmados, al principio particularmente los cambios eran contnuos y la estabilidad algo
dudosa. Pero eso ha cambiado! Dado el gran apoyo de muchas empresas y el inters general que
existe por Node, cada vez es ms estable y robusto y los cambios no suelen ser tan drsticos.
Suelen sacar versiones bastante a menudo con la intencin de solucionar errores y, especialmente,
mejorar el rendimiento. No obstante, a veces, el cambio de versin es bastante significativo e implica
que ciertas funciones dejen de funcionar como lo hacan 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 produccin, 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 versin 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 vers comandos como ls que no funcionarn en
Windows. Aunque no es algo que ocurrir muy a menudo e intento minimizarlo lo mximo posible,
tenlo en cuenta si ests siguiendo el libro en Windows.
https://github.com/joyent/node/wiki
http://blog.nodejs.org/
Introduccin vi
Audiencia del libro
Este libro est pensado para lectores que tengan algo de experiencia con algn lenguaje de
orientacin 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, cmo funciona un bucle for o qu valores
se evalan 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 ms, 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 ests 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 tendrs un objeto window
ni dispondrs del mtodo 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 ms modernas de JavaScript
que solo estn disponibles en los ltimos navegadores.
Observa que el ttulo 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 intencin 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 Ests preparado para sumergirte en el mundo de Node.js? Pues adelante, sigue leyendo!
Nos vemos en el primer captulo!
Introduccin a Node.js
En la introduccin 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 ms.
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 mdulo de pop-ups por ac y
un slideshow por all. Qu hay en comn 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 pgina, no es con eso con lo que te vas a encontrar aqu.
Node.js es JavaScript del lado del servidor. Realmente es algo ms 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 ms sencillo compararlo con un entorno PHP, Ruby o Python (todos amigos por igual).
Para trabajar con Node.js, nos valdremos de la lnea de comandos en muchas ocasiones ya que, en
el fondo, no es ms que una interfaz de lnea de comandos. Te vas a sentir todo un hacker! S que
muchas personas le tienen miedo a la lnea de comandos, pero como dice Bane
1 Yo nac en la lnea de comandos, moldeado por ella. No vi Windows hasta que\
2 ya era un hombre...
Creme cuando te digo que no tienes por qu preocuparte, ya que no vamos a usar ningn comando
raro en ningn caso. Confiars 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 parchs. Si no de operaciones que bloquean (o no) el
servidor. Vamos a meternos un poco ms en faena.
Node.js basado en eventos
La aplicacin habitual de Node.js, se queda residiendo en memoria y espera algo por parte del sistema
para ejecutar una funcin. Esto puede ser alguien entrando en una pgina 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 mayora de los lenguajes. Son como eso que imaginas
solo que no son personas. Es un proceso que se encarga de revisar el cdigo que est en ejecucin y
decir
1 A ver, esta variable sigue existiendo? No, no? Pues la quitamos de la mem\
2 oria
3 Y esta funcin 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 quedaramos sin memoria y eso sera una catstrofe. Todo empezara a ir lento. No solo la
aplicacin! Si no el sistema!
Y puedo yo llamar al recolector de basura?
No, no puedes ni debes. Si seguimos buenas prcticas de programacin no tendramos nunca que
preocuparnos por este proceso invisible.
Vale Antonio, pero todo esto no lo puedo hacer con Python o PHP?
Seguro que ests pensando en eso. Mucha gente lo piensa, especialmente los Javasaurios. Y tienes
razn. 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,
ms o menos. Vas a tener que confiar en mi, un pequeo salto de f. Si lo hiciste con Altair, seguro
que puedes lograrlo!
La asincrona por naturaleza
En Node.js hay dos formas de hacer las cosas. Asncronas o sncronas.
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 sncrono, meteras la tostada. Cuando saliera del tostador,
comenzaras a hacer el caf. Cuando el caf saliera de la cafetera, untaras la tostada de mantequilla.
Cuando terminaras calentaras algo de leche. Finalmente te podras tomar el desayuno solo que,
probablemente, el caf que te eches en la taza est un poco fro y la tostada est fra al untarle la
mantequilla.
En un mundo asncrono 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.
Vemoslo ahora con un ejemplo de cdigo:
Ejemplo 1: Cdigo tradicional sncrono
1 var resultado = db.query("SELECT * FROM usuarios");
2 // Esperamos a los resultados
3 hacerAlgoConEllos(resultado); // Esperamos a que termine
4 hacemosOtraCosa();
Ejemplo 2: Cdigo asncrono
1 db.query("SELECT * FROM usuarios", function(resultados){
2 hacerAlgoConEllos(resultado); // Esperamos a que termine
3 });
4 // Esta funcin se ejecuta justo despus 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 asincrona por naturaleza 4
Aqu est el engao
Te dije que confiaras en mi. En apariencia se ejecutan a la vez. Sin embargo, tras el teln, 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 rpido 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 tcnicas, lo que realmente ocurre es que JavaScript, tanto el cliente como en el
servidor, se ejecuta en un nico hilo, por lo que es tcnicamente imposible que haga dos cosas a la
vez. Pero confa en mi, esto no es ningn problema.
Esto puede resultar un poco confuso, especialmente al principio. Pero si me vas siguiendo no deberas
perderte en el camino de la asincrona.
Instalando Node.js
Para no aturdirte en demasa, 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 vers es un proceso realmente sencillo.
Instalando en Windows y Mac
El proceso de instalacin en ambos sistemas es exactamente el mismo.
1. Nos dirijimos a la pgina de Node.js: http://nodejs.org/
2. Le damos al botn 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 instalacin que es bastante sencillo.
5. Profit.
Instalando Node.js 6
Figura 1: Instalacin de Node.js en Mac OSX
Instalando en Linux
Cubrir cada distribucin de Linux es una locura, pero el proceso no debera variar mucho. En este
caso, vamos a ver cmo instalar Node.js en Ubuntu.
1. Nos dirijimos a la pgina de Node.js: http://nodejs.org/
2. Le damos al botn de DOWNLOAD.
3. En la siguiente pgina, tendremos que elegir el cdigo 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, deberamos 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
Atencin
Puede que necesites realizar sudo para realizar algunas acciones. Ten en cuenta adems,
que el proceso puede llevar algo de tiempo. Si necesitas ms informacin para instalar
Node.js en alguna versin de Linux en partcular, revisa las instrucciones de instalacin
en la wiki de Node.js.
Funciona mejor Node.js en algn 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 peazo, por no decir una palabra ms fea. Haba
que usar compiladores y armarse de paciencia.
Por suerte, Microsoft entendi que no se poda quedar atrs en esto, y se ali con Joyent (la empresa
detrs 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 deberas 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 estbamos instalando Node.js. Lo que no te he contado es que adems,
acabamos de instalar npm. Esto ocurre de forma automtica desde la versin 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, debers ver algo como:
1 v0.10.11
Bien! Ahora podemos seguir.
Accediendo a la consola
Tanto en Mac como Linux, la consola no es ms que el terminal del sistema y suele llamarse Terminal
a secas. Especialmente en Linux estars 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 versin modificada del cmd que nos crea directamente Node.js al
instalarla. Esta versin no tiene nada de especial, solo que nos aade algunas variables de entorno y
nos inicia automticamente 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 intntalo!
Para acceder a ella no tienes ms que ir a la carpeta que el proceso de instalacin habr creado en
el men de inicio:
Figura 2: Consola de Node.js en Windows
Hola mundo!
Antes de pasar a explicar nada ms, 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 prctica, debera! 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
Ingls.
Primero escribe node en la consola.
Ya? Si todo ha ido bien, debera quedarse con un smbolo > 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 cmo lo explico.
undefined es solo que la sentencia que acabamos de escribir, no devuelve ningn 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, vers 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 tambin seguro que lo sabes, parseInt intenta transformar una cadena en un nmero
y lo devuelve as que, en esta ocasin, veremos que nada ms darle al Enter nos devuelve un 30.
Ahora que ya hemos probado esto rpidamente, veamos el otro mtodo.
Hola mundo! 10
Pero cmo 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 descubrirs, es como lo haremos habitualmente, colocando el cdigo
en un archivo. No creeras 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 aplicacin.
1 $ mkdir holamundo
2 $ cd holamundo/
Ahora que ya estamos dentro de la carpeta, y si ests 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 convencin para indicar que es el archivo que contiene la aplicacin y, por
tanto, el ejecutable.
Ahora abre el archivo con tu editor de texto preferido (si es que no lo habas hecho ya) y escribe
dentro lo siguiente:
1 console.log('Hola mundo!');
Gurdalo, y en el terminal escribe:
1 $ node app.js
Esta vez deberamos ver el mensaje sin el undefined de antes y, tras aparecer, la aplicacin se cerrar
sin ms. Fcil! no?
Hola mundo en un servidor!
Recuerdas el ejemplo que pusimos en la introduccin del libro? Vamos a revivirlo. Vamos a crear
vida! Quita todo el cdigo 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
Vers que en la consola aparece el mensaje Servidor escuchando por el puerto 8080 que hemos
puesto en la lnea 8. Ahora accede a http://localhost:8080 y, si todo ha ido bien, deberas ver una
pgina que dice Hola Mundo!.
Veamos el ejemplo lnea a lnea:
En la primera lnea 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 mdulos. Es similar a include de PHP, #include de C o import en Python. Quiz te ests
preguntando porqu difiere de include si tanto PHP como C usan include. El motivo es que require
es una sentencia comn 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 mdulo http que, casualmente, es un paquete del
ncleo 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 mdulos del mismo modo
que se llama el mdulo, recuerda que es solo una convencin para mantener la cordura.
Siempre puedes poner var ptth = require('http'); si quieres volver loca a la gente
que lea tu cdigo.
Lo siguiente que hacemos es crear un servidor valindonos de la funcin createServer que nos
facilita http. sta recibe como parmetro una funcin, en este caso annima, con dos parmetros
Hola mundo! 12
request, que es la peticin, y response que es la respuesta que tenemos que enviar. Esa funcin 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 peticin debe tener un cdigo 200, que es el cdigo HTTP para
decir que la peticin es correcta, y que el tipo de contenido que tiene que esperar el navegador es
texto plano. Adems, finalizamos la respuesta con el mtodo end, aadiendo el texto que queremos
devolver.
Qu pasara si no llamramos a end?
Si no llamramos al mtodo end de la respuesta, la peticin nunca terminar y el
navegador acabar por dar un error porque sigue esperando que le enviemos datos. No
me crees? Prubalo y vers!
Antes dije: en este caso annima. Ten en cuenta que este cdigo sera totalmente equivalente y
vlido:
Funcin no annima
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
Funcin 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 funcin createServer recibe otra como parmetro y se encargar de ejecutarla cuando haya
terminado su trabajo. Esto se conoce como callback. Es un patrn muy comn cuando se hacen
cosas asncronas ya que, sabemos cundo se comienza a ejecutar el cdigo, pero no sabemos cundo
va a acabar.
En Node.js, los callbacks por norma general tienen la siguiente estructura. El primer parmetro de
un callback debe ser un error si lo hubiere, en caso contrario null. A continuacin, le siguen el resto
de parmetros.
Espera Antonio, entonces Node.js no est siguiendo su propio patrn?
No es eso. Lo que realmente ocurre es que la funcin que estamos pasando ms que un callback es
algo que est a la escucha para cuando llega una peticin al servidor. El servidor tiene sus propios
callbacks internos a la hora de realizar tareas asncronas y, si hay un error, se encargar de gestionarlo
l por lo que nuestra peticin debera llegar sin ms.
En la siguiente lnea, le decimos por el puerto por el que escuchar, en este caso el 8080. Puedes elegir
cualquier otro puerto que no est en uso. Quin soy yo para decirte que no uses el puerto 6969 para
tu desarrollo? Nadie!
Como vers, 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 habrs visto que detrs de createServer tenemos una llamada a la
funcin 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 comn cuando apareci jQuery.
La consola de Node
Si habis estado atentos, habris visto que hemos estado usando console.log y que, la informacin
que hemos puesto, apareca en el terminal mientras ejecutbamos la aplicacin.
Si has trabajado con JavaScript (cosa que espero) quiz ya ests acostumbrado a console puesto que
es una forma de mostrar en la consola del Navegador informacin para ver por dnde va pasando
nuestro cdigo.
console sirve para escribir en la salida estndar y en la de error. sta nos ayudar enormemente en la
tarea de saber qu es lo que est pasando en las entraas de nuestra aplicacin sin ser excesivamente
complicado como podra 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 mtodo sncrono por lo que, si abusamos brutalmente de ella, estaremos creando
bloqueos en nuestra aplicacin.
Hay libreras estupendas para registro de eventos y logging en general para Node, como winston.
No obstante, para el propsito 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 informacin 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 informacin al ms puro estilo sprintf de PHP.
Imagina que quieres mostrar el nmero de planetas que hay en nuestro sistema solar, pero en vez
de poner un nmero ah en plan soso, con una frase para dotarlo de sentido. Podras 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 sera mejor
tener que evitarnos todos esos +? Yo creo que s. Vamos a ver cmo 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 parmetros extra que sustituir en orden
de aparicin. Ten en cuenta que si pasas ms parmetros de los que realmente pones, Node.js
smplemente lo pondr al final.
1 var planetas = 8,
2 expulsado = "Plutn";
3
4 console.log('Hay %d planetas',planetas,expulsado); // Hay 8 planetas Plutn
Vamos a arreglarlo, pasando un parmetro ms:
1 var planetas = 8,
2 expulsado = "Plutn";
3
4 console.log('Hay %d planetas porque %s ya no es uno de ellos',planetas,expu\
5 lsado);
6 // Hay 8 planetas porque Plutn 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 ms!
Patrn Tipo
%d Enteros y coma flotante
%s Cadenas
%j Objetos JSON
Vale, no es que sean muchos ms, 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 mtodos 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 envan a la de errores.
Qu es eso de la salida de errores?
La salida de errores es una salida alternativa. Esto permite ms flexibilidad puesto que puedes tener
dos tipos de logs. Uno para errores (que sern importantes) y otro de informacin (ms trivial). Esto
es especialmente til cuando tienes multitud de mensajes mostrndose por tu aplicacin y t solo
quieres centrarte en los problemticos.
console.time y console.timeEnd
Estas funciones las usaremos para hacer pruebas de rendimiento de nuestro cdigo. La primera
funcin es como si agarrramos un cronmetro, lo pusiramos a 0 y empezramos a contar.
1 console.time('Operacin costosa');
2 // Aqu realizamos una operacin costosa
3 console.timeEnd('Operacin costosa');
timeEnd es como si parramos ese cronmetro, mirramos el tiempo y lo anunciramos al mundo.
Cuando se ejecute, veremos aparecer algo como:
La consola de Node 18
1 Operacin costosa: 33ms
Esta es una forma realmente conveniente de saber dnde podemos tener algn cuello de botella en
nuestro cdigo.
Accediendo a las variables del
entorno
Quiz ni siquiera sepas lo que es una variable de entorno. No te avergences. Es una cosa que hoy
en da 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 cmo acta en
s el sistema.
Una de las variables ms 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,
podrs ejecutar una aplicacin 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 adems la oportunidad de crear una variable que solo ser visible dentro
de Node.js, en esa ocasin que la lancemos.
Lo ms habitual es establecer la variable NODE_ENV, que establece el tipo de entorno en el que estamos
trabajando que normalmente ser production (produccin) o development (desarrollo):
1 NODE_ENV=production node app.js
Todas las variables del entorno estn 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 aplicacin, en funcin del entorno en el que estemos, haramos 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 aplicacin, o bien al reiniciar el equipo. Si quieres aadir las variables de forma
permanente, tendrs que valerte de /.bash_profile en sistemas Unix o cambiarlas en
las propiedades del sistema en un entorno Windows.
Pasando parmetros a Node.js
Adems de valernos de las variables del entorno, podemos pasarles parmetros a Node.js de la
siguiente forma:
1 $ node app.js [argumentos]
Este tipo de tcnicas es especialmente til si, en vez de crear un servidor, creamos algn tipo de
aplicacin que haga un proceso en funcin de uno o varios parmetros. Por ejemplo, imagina un
pequeo script que compare dos ficheros de texto y cree un tercero con las diferencias. En ese
caso, podras pasar los dos nombres de los ficheros a comparar y, un posible tercer parmetro,
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 mayora de las ocasiones, estos argumentos no nos interesan para nada. Por ello, podramos
hacer algo as:
1 var argumentos = process.argv.splice(2);
Por si no ests 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 cdigo:
1 var argumentos = process.argv.splice(2);
2 console.log(argumentos);
Si llamramos a la aplicacin de esta forma: node app.js uno dos tres el resultado sera:
1 [ 'uno', 'dos', 'tres' ]
Pasando parmetros a Node.js 22
Ya que ha eliminado los dos primeros iniciales, que no nos interesaban.
De inters
En caso de que quieras crear un programa de consola, al que le puedas pasar varios
parmetros, un buen mdulo que he usado en alguna que otra ocasin con bastante
xito es Optimist. Una librera que te permite parsear opciones, aadir alias, parmetros
obligatorios, men de ayuda automtico, etc.
https://github.com/substack/node-optimist
NPM - Node Packaged Modules
Como ya hemos comentado, npmes instalado con Node.js de forma automtica. S, sin que tengamos
que hacer nada de particular. Lo bueno es que cuando actualicemos Node.js, npm lo haga tambin
de forma automtica.
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 actuacin con paquetes
de node. Buscarlos, instalarlos, actualizarlos, etc. Es posible que hayas utilizado algn gestor de
paquetes con anterioridad. Los hay para varios sistemas:
PHP - Composer
Python - Pip
Ruby - RubyGems
OS X - Homebrew
Vamos a ver cmo podemos hacer algunas de estas operaciones con npm. Especialmente las ms
tiles.
Bsqueda de paquetes
En ocasiones querremos buscar algo. No sabemos cmo se llama el mdulo pero sabemos que
queremos algo para gestionar uhmm asincrona! Si el programador del paquete lo ha hecho bien,
debera 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 ms 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 descripcin 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 lnea de comandos. Ests hecho un hacker!
Aunque no sea tan emocionante, puedes hacer la misma bsqueda en https://npmjs.org/ ya que es
donde npm search busca sus paquetes
Obtener informacin de paquetes
Ahora que tenemos una lista de paquetes, seguro que quieres saber algo ms 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 rarsimo. Para saber los detalles de un paquete, escribiremos npm
view {nombre del paquete}.
1 $ npm view abiogenesis
Esto realmente nos devuelve una visualizacin 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
pequeo 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 fantstico framework que ejecuta tareas asncronas y anidadas con resolucin de
dependencias! Tendr ziritione?
Lista de paquetes instalados
En ocasiones querrs ver la lista de paquetes que tienes instalados en tu aplicacin. 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 versin es 1.3.3. Abajo, tenemos otro paquete, ycssmin. Este paquete,
por el grfico, 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 tambin 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 vers todos los paquetes globales que tienes instalados, con sus dependencias por supuesto.
Instalacin 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 seccin pero all vamos. Tenemos
dos tipos de instalaciones: La instalacin global y la instalacin local.
Veamos la diferencia.
Los paquetes locales son aquellos que se instalan sobre la aplicacin en la que ests trabajando en
el momento en que ejecutas el comando. Si por lo que sea la instalas en una carpeta que no debas,
se instalar igualmente puesto que npm no distingue si ests o no en una aplicacin. Los paquetes
locales solo estarn disponibles en el paquete actual. Normalmente es la forma ms 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 lnea de comando. En esos casos tendremos que instalarlo de forma global. Hacerlo
es realmente sencillo ya que nicamente tenemos que aadir 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().
Cmo decidir qu tipo de instalacin realizar
Piensa que, en general, una instalacin global no es algo bueno. Seguro que, trabajando con
JavaScript has ledo que las variables globales son malas. Las instalaciones globales tambin.
Bueno, no es que sean malas, solo que es algo que no tendramos que tomar a la ligera.
Entonces, cmo puedo decidir?
Veamos unos consejos:
Si quieres instalar un paquete que funcione dentro de tu aplicacin con require(), instlalo
de manera local.
Si ests instalando un paquete que puede ser usado desde la lnea de comandos como un
programa o algn uso extendido (como en el caso de Express), instlalo de manera global.
Algunos de los paquetes que suelen instalarse de manera global:
Express
forever
nodemon
Bower
Grunt
Uglify-js
Webs de instalacin 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, permitindote ordenarlos por Fecha de Modificacin
(til para saber si siguen o no activos), nmero de forks, estrellas, etc.
Como alternativa a Nipster, encontramos Gitmoon, que nos ofrece mucha ms informacin
de la que podamos encontrar en Nipster sobre cualquier paquete que queramos buscar como la
dependencia entre proyectos, quin usa el paquete, etc.
Si descubres (o creas!) alguna web que creas que puede cumplir con estas funciones, hzmelo saber!
http://eirikb.github.io/nipster/
http://www.gitmoon.com/
NPM - Node Packaged Modules 27
Desinstalacin 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 tena 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 ms.
Paquetes tiles y habituales
nodemon - Realmente til durante el desarrollo ya que relanza la aplicacin con cada cambio
que hagamos en el archivo, evitando tener que lanzar y detener el proceso contnuamente.
mongoose - Si trabajas con mongoDB, este es sin duda uno de los mdulos que querras
usar para guardar tus objetos ya que su sintaxis es realmente sencilla y fcil de comprender.
node-mysql - Si eres ms 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 fcil. Este mdulo
permite SMTP, Amazon SES y la funcin sendmail que tenga el sistema. En su pgina
encontrars ejemplos sobre cmo hacerlo funcionar con Gmail por ejemplo.
node-validator - Validar campos de un formulario es una tarea que, ms tarde o ms
temprano, acabaremos realizando. Este mdulo nos ayuda a validar campos y a sanearlos
eliminando caracteres blancos, escapndolos, etc.
Crees que hay algn paquete que deba estar aqu? Hzmelo 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
..
Cmo actualizo un paquete?
Actualizar un paquete es realizar la misma tarea de instalacin, es decir npm install {nombre del
paquete} o npm install -g {nombre del paquete} si queremos actualizarlo globalmente. Asegrate
de revisar las notas de la versin antes de realizarlo, especialmente si es una aplicacin en produccin,
ya que puede que hayan eliminado funciones o cambiado el funcionamiento de algo.
Cmo mantener Node.js actualizado
Si has ledo al principio del libro, ya sabrs que Node.js se actualiza con bastante frecuencia. A veces,
incluso un par de veces a la semana.
La pregunta lgica que surge es cmo mantengo Node.js actualizado?
Ciertamente, podras 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 mtodo 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 lnea de comandos.
1 $ npm install -g n
Una vez que tenemos instalado n ya podemos comenzar a usarlo. Para actualizar la versin 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 actualizacin solo
tengo que lanzar el comando de arriba, esperar un ratillo y disfrutar de una nueva versin.
Lamentablemente n no funciona con Windows. Ms lamentablemente aun, en su Github no aparece
ninguna incidencia al respecto por lo que parece que no tienen inters en arreglarlo.
n tiene otros comandos que te pueden servir:
Comando Accin
n latest Instala o activa la ltima versin de node
n stable Instala o activa la ltima versin 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 versin disponible de node
n --stable Muestra la ltima versin estable de node
n ls Muestra las versiones de node disponibles
https://twitter.com/nodejs
Cmo mantener Node.js actualizado 30
Personalmente he usado a veces n <version> ya que en ocasiones, me han colado como estable
una versin y luego resultaba que me haban roto algo que no me dejaba funcionar correctamente.
Sobre las versiones de Node.js
Cuando aparece una nueva versin 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 versin viene con la cadena al lado (Stable) o (Unstable) junto a una escueta explicacin de los
cambios introducidos. En la mayora de las ocasiones no nos aportar mucha informacin ya que
suelen ser mejoras de rendimiento y errores corregidos. Pero tendrs que dar un salto de fe. Pensar
que lo estable es realmente estable e instalarlo.
A veces, cuando ocurre un cambio de versin mayor como el que ocurri al saltar de la versin 0.8
a la 0.10, podemos encontrar un artculo mucho ms detallado e incluso una pgina en GitHub
donde nos detallan las diferencias entre versiones.
Recuerda visitar siempre el blog de Node.js antes de realizar ninguna actualizacin de versin!
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 aplicacin de
Node.js
Ahora que ya tenemos conocimientos bsicos, vamos a ponernos manos a la obra no? Recuerdo
cuando empec a estudiar programacin y todo el mundo le preguntaba a la profesora: cundo
vamos a poder programar? para qu tanta teora? As que, no me enrollo ms, pongmonos 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 captulo.
Cdigo 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 parmetros no ha sido especificado.');
10 }
11 else {
12 switch (operacion) {
13 case '*' : {
14 nombreOperacion = 'multiplicacin';
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 aplicacin de Node.js 32
26 break;
27 }
28 case '/' : {
29 nombreOperacion = 'divisin';
30 if (valor2 !== 0){
31 resultado = valor1 / valor2;
32 }
33 else {
34 console.error('Qu queras? Cargarte el universo?!')
35 }
36 break;
37 }
38 default : {
39 console.error('La operacin %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 lneas para
este cometido. Como ves, nos hacemos con todos los argumentos que le hayamos pasado a la
aplicacin, como hemos aprendido y declaramos algunas variables vacas que contendrn valores
una vez que realicemos la operacin en s.
En caso de no obtener alguno de los tres parmetros que necesitamos (dos valores y la operacin)
lanzaremos un error para que el usuario sepa que no lo est haciendo correctamente.
Gracias a un switch, encauzaremos nuestra aplicacin por el buen camino. Como puedes ver hemos
implementado 4 operaciones: suma, resta, multiplicacin y divisin. En caso de que no sea ninguna
de ellas, devolveremos un error.
Como vers, no hacemos comprobaciones excepto para la divisin, 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 operacin y su
resultado, dando formato a la consola.
Hagamos un par de pruebas:
Nuestra primera aplicacin de Node.js 33
1 node app.js '*' 3 4
2 // El resultado de la multiplicacin es 12
3 node app.js '+' 997 3
4 // El resultado de la suma es 1000
5 node app.js '^' 2 2
6 //La operacin ^ no ha sido implementada
Parece que funciona! Nada difcil hasta ahora, no?
Ejercicio
Intenta aadir una nueva operacin: la potencia en el que el primer parmetro sea la
operacin, el segundo el valor a elevar y el tercero la potencia que queremos usar. Hay
que hacer alguna validacin especial?
Resumen
Hasta aqu nuestro primer captulo. Si has seguido hasta aqu el libro, habrs aprendido cul es la
esencia de una aplicacin de Node.js y cmo funciona la asincrona. Si no has terminado de entender
el concepto, te recomiendo que le vuelvas a echar un vistazo porque es de las cosas ms importantes
de Node.js y es necesario que el concepto quede claro.
Despus, hemos visto cmo hacer uso de la consola de node para imprimir texto y mostrar contenido
de los objetos a travs de los mtodos que nos ofrece console. Hemos visto la diferencia entre la
salida normal y la de errores y cmo medir el tiempo con la consola. Sin duda mtodos muy tiles
para ver qu es lo que va ocurriendo en nuestra aplicacin
Adicionalmente, hemos visto cmo podemos valernos de las variables del entorno para poder
modificar el comportamiento de nuestra aplicacin en funcin del valor de alguna de las variables.
Adems, has aprendido a pasar parmetros a tu aplicacin de Node.js para ayudar a que tu aplicacin
funcione.
Adems, habrs aprendido a instalar Node.js y npm y cmo 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, cmo 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 captulo
y nos hemos asegurado de que funciona correctamente.
En el siguiente captulo, veremos temas ms avanzados como exportacin/importacin de ficheros
en nuestra aplicacin con require, organizacin de los archivos de nuestra aplicacin, emisin de
eventos y ms cosas que tienes por descubrir. No te las pierdas!
Adentrndonos en Node.js
Ahora que ya tenemos los conceptos bsicos sobre Node.js, podemos empezar a jugar con conceptos
y funciones que son ms complejas. Estoy seguro de que es lo que estabas deseando, verdad? Anadie
le gustan las presentaciones, son aburridas y odiosas pero, al menos en este caso, estaba totalmente
justificada. Lo prometo!
Este captulo 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 chchara. Vamos a ponernos manos a la obra!
Gestin 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 aplicacin, la puedas distribuir fcilmente a travs
de npm para que pueda ser instalada por cualquiera.
Pero hay una cosa que tienes que tener en cuenta. Tu aplicacin 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 ms 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 travs de npm,
viene sin la carpeta node_modules en la cual se instalan todas las dependencias.
Es probable que te ests preguntando
Si Express usa como dependencia a connect, puedo hacer yo require('connect')y
usarlo yo tambin?
Realmente no. Tampoco es buena idea. La gestin de dependencias en cadena que usa Node.js (y la
mayora de los lenguajes), est pensada as para que las versiones no den conflictos entre si. Voy a
intentar explicarlo con un ejemplo ms grfico.
Imagina por un momento, que eres carpintero. La programacin es muy complicada y has decidido
buscar fortuna con otra profesin. Te has bajado de Google un diseo de rueda de carruaje
espectacular as que, diseas tu carruaje alrededor de ese diseo 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 compaero 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 versin
de la rueda que te descargaste de Google. La otra no funciona. Ciertamente podras 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 da de maana, Express actualiza su versin de connect es muy probable que tu aplicacin
deje de funcionar correctamente por lo que, lo mejor, es especificar la versin que quieres usar en
tus dependencias.
Gestin de dependencias con package.json 37
En el archivo package.json es donde colocamos toda esta informacin adems de ms 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 semntico
Antes de meternos con el archivo en s, me gustara hablar del versionado semntico (semver), que
es usado tanto por Node.js como por los paquetes y tendremos que usarlo en cuanto definamos las
dependencias. Siguiendo nuestro ejemplo, sera la forma de catalogar la versin de las ruedas.
Cuentan las leyendas, que en tiempos ancestrales no exista esta forma de versionar el software.
Todos recordaremos la famosa etiqueta Beta de Gmail que, en aquel momento quera decir: Esto
funciona pero vamos a aadir ms cosas y eventualmente puede romperse! (eso no es lo que hoy en
da significa Beta).
Adems, tenamos 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 da, las cosas han cambiado un poco conforme las metodologas han ido avanzando. Por
ello, tambin lo ha hecho la forma en que ponemos las versiones de las versiones (vlgame la
redundancia). Hoy en da, 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 semntico).
x - Cambio mayor : Este tipo de cambio es un cambio serio en la aplicacin de manera que
es drsticamente diferente de anteriores versiones. Normalmente, si esta fuera una aplicacin
de pago, es el tipo de cambio que te costaran dinero.
y - Cambio menor : Has aadido algo nuevo que mejora significativamente el producto o
bien has cambiado algo que estaba causando muchos problemas y/o era malicioso o si ests
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 metodologa que se usa para versionar cada uno de los
paquetes que podemos instalar con npm. No obstante, aceptan una modificacin, el - por lo que
este tipo de versin 0.30.1-2 es vlida para paquetes npm, pero no en el mundo semver. A mi
personalmente este aadido me parece superfluo ya que, nada te impide aumentar la versin z para
hacer ajustes, no?
En general, no deberas usar 1.X.X a no ser que la aplicacin sea estable. Seguro que dudas de si tu
aplicacin 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 aplicacin en produccin?
Gestin de dependencias con package.json 38
Si has respondido s, es que ya debera haber pasado a esa versin as que Corre!
Y no est Node.js en produccin?
Hombre no nos pongamos quisquillosos. En realidad, Node.js no es que est en produccin si no
que se usa para aplicaciones que se ponen en produccin. No obstante, podramos considerarlo como
en produccin. Lo que ocurre, es que est en continuo cambio y hay muchas partes del cdigo que
aun no funcionan todo lo estable que le gustara al equipo que est detrs 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
aaden al cdigo ms significado. Para crearlas, solo tienes que aadir al final la versin que quieras.
Por ejemplo 0.1.2-3-beta o 0.1.2-4-alpha. Aqu tienes un ejemplo de cmo evala 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 versin ms actualizada del paquete disponible,
mientras no seas ms especfico.
Descubre ms
Si te interesa saber ms sobre la especificacin, puede que quieras echar un vistazo a lo
que te cuentan en su pgina web (en ingls) 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 vacos ni caracteres extraos. S, la cuenta como caracter,
puetas!
El segundo elemento no es otro que la versin del paquete que estamos creando. Puedes usar por
ejemplo la 0.0.1 para empezar.
No obstante, la tarea que ms nos preocupa en este captulo, no es otra que gestionar las dependencias
de nuestra aplicacin. As que, aadamos a la lista de imprescindibles, la dependencia de mdulos
(con sus versiones) que necesitamos para usar el paquete.
Veamos un breve ejemplo:
http://semver.org/
Gestin 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 parmetros son bastante sencillos de entender. Todos estos parmetros son vlidos en
este archivo y, lo que hacen, es dotar de ms significado al paquete en s. Si fueramos a distribuir el
paquete para que lo pudieran descargar terceros, sera sin duda buena idea el aadir estos parmetros.
Como ves, le estamos facilitando un nombre, una descripcin, un autor (ficticio) ylas dependencias.
Como habrs observado, las dependencias estn especificadas con notacin semver.
Quiz te hayas fijado en que la dependencia que hemos indicado de Express es de la versin 3.2.0
o mayor.
Para instalar las dependencias solo tenemos que ejecutar el siguiente comando:
1 npm install
Yvers como automticamente empieza a bajarse las dependencias indicadas. En este caso solamente
Express. Node intenta buscar la versin ms actualizada posible que cumpla con los requisitos que
hemos puesto para cada paquete. Si por lo que sea no hay versin del paquete que cumpla con los
requisitos, la instalacin fallar.
Aunque hayamos indicado que queremos las versiones mayores a 3.2.0 esto no significa que sea
una buena prctica. Realmente estamos diciendo, quiero estar a la ltima y, cada vez que ejecutemos
el anterior comando nos traeremos la ltima versin de la dependencia genial, no?
No
El da que por algn casual salga Express 4.0.0 es muy probable que tu aplicacin 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
Gestin de dependencias con package.json 40
Dependencias para desarrollo
Imaginemos que queremos, por algn casual, instalar dependencias que luego no vayamos a instalar
en produccin, pero adems, 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 mayora de este tipo de dependencias
son para instalar software que nos permita realizar pruebas sobre nuestra aplicacin.
Para ello, podemos aadir un parmetro ms 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 ms 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 instalarn. 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 opcin:
1 $ npm install --dev
Instalar un paquete y guardarlo como dependencia
Como aprenders 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 encontrars 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:
Gestin de dependencias con package.json 41
1 $ npm install node-mysql --save
2 $ npm install mocha --save-dev
Estas marcas opcionales, nos permiten actualizar automticamente nuestro archivo package.json.
--save se encargar de guardarlo en la seccin de dependenciesmientras que --save-dev lo
guardar en devDependencies.
Esto si que es un gran amigo!
Descubre ms
Si te interesa saber ms sobre el archivo package.json, puedes encontrar una gua
interactiva en la que tratan todos los campos que se pueden usar (muchos ms de los
que hemos visto aqu) en la pgina de Nodejitsu - No te la pierdas!
http://package.json.nodejitsu.com/
Exportando en Node.js
Hasta ahora hemos estado importando paquetes a nuestra aplicacin cigamente. Por ejemplo:
1 var util = require('util');
Pero, qu pasa si queremos crear un archivo que queramos requerir? En este captulo vamos a ver
como hacerlo. Separar el cdigo es buena idea y es algo que trataremos en el prximo captulo con
todo detalle.
Node.js implementa el estndar que cre CommonJS para cargar mdulos, esto facilita mucho la
tarea de exportar e importar archivos en Node.js. Como no pretendo que nadie que lea este libro
conozca el estndar 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 Crepscu\
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 cmo se va comportando la variable exports. Podemos
aadir tantas propiedades y funciones como queramos. Estas funciones tendrn 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 aadimos a la cuenta de Tweets
Veamos cmo 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 hubiramos guardado
en la carpeta aplicaciones, tendramos que haber puesto algo como aplicaciones/tweets. La
extensin 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 podrs comprobar, no tenemos acceso al valor de misOtrosTweets pero la funcin
cuentaTweets ha accedido a su longitud correctamente.
Cabe destacar que el llamar tweets a la variable es solo una convencin de llamar a la variable igual
que al fichero que se importa, podramos 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
pequeo 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 aadirlas a
Exportando en Node.js 44
module.exports si ste ltimo no tiene ya algo. Si hemos aadido algo a module.exports los
exports sern 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 intil aplicacin ha obviado por completo la funcin cuentaTweets ya que
module.exports haba tenido lugar.
module.exports puede tomar el valor de cualquier cosa de JavaScript: ya sea una funcin, 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 cdigo de la clase a otro archivo y ahora podemos crear objetos de ese
tipo fcilmente, 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 cdigo, 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 mtodos 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 idntico 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 mtodo adecuado
En este momento estars teniendo un cacao mental y estars pensando y cundo elijo uno u otro?
Yo he estado ah. La verdad es que es cuestin 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 configuracin, haremos
algo as:
Exportando en Node.js 47
config.js
1 module.exports = {
2 usuario : 'root',
3 clave : 'root',
4 servidor : 'localhost'
5 };
Pasando parmetros a require
A veces puede que quieras personalizar lo que obtienes de un require pasando un parmetro. Esto
puede ayudarte enormemente a reducir la cantidad de cdigo que necesitas escribir. Pongamos
el ejemplo de que necesitas conectar a dos bases de datos y tienes un mdulo 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 conexin por defecto y luego exportamos la
funcin. La funcin es una de esas joyas de JavaScript: una funcin que devuelve funciones dawg!
Bsicamente estamos protegiendo el valor de las variables y asegurndonos de que si no pasamos
alguno de los parmetros, tengamos un valor por defecto. Esta funcin a su vez devuelve un objeto
(que es lo que se queda al final), con una funcin 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 cdigo que pueda ser reusado por otras partes de la
aplicacin, lo cual ayuda enormemente a que el cdigo sea ms sencillo de mantener.
http://i.imgur.com/TWmBWl8.jpg
Organizando el cdigo de nuestra
aplicacin
La forma en que organicemos el cdigo de nuestra aplicacin es bastante subjetiva y suele ser algo de
preferencia personal. No obstante recuerdo que, cuando comenc con Node.js, todo el cdigo estaba
en app.js y en principio todo pareca funcionar. Pasa el tiempo y la aplicacin crece y quieres reusar
cdigo o cambiar algo y poco a poco tu dedo del scroll del ratn empieza a quejarse ests haciendo
mucho scroll por el cdigo!
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 ningn mdulo de Node.js te
fuerza a usar ninguna estructura en particular. No hay ninguna buena estructura reconocida aunque
con un poco de buenas prcticas y experimentacin, te hars 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 cdigo es, cmo podra separar
esto? es esto un mdulo con el que usara require para traer esa funcionalidad?
Si la respuesta a esa pregunta es s, deberas buscar primero si ya hay alguna librera en el mercado,
que te pueda servir. En caso de que no, porque sea cdigo particular a tu aplicacin, tendrs que
escribir la tuya propia y ms adelante, si lo crees necesario, podras 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 aplicacin.
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 aplicacin, por ejemplo Tweet o
Usuario.
lib : En esta carpeta nos encargaremos de guardar todas las libreras que creemos. Y por
libreras nos referimos a funcionalidades de la aplicacin en concreto.
Organizando el cdigo de nuestra aplicacin 50
db : Aqu guardaremos el cdigo relacionado con la conexin a la base de datos, sea esta
MongoDB o MySQL o lo que sea.
config.js : Aqu guardaremos la configuracin de la aplicacin, normalmente en un objeto
sencillo y, habitualmente, diferenciando si la configuracin es en produccin 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 imaginacin para poner esto un poco ms en contexto. Imaginemos
que nuestra aplicacin 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 librera 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 aadir ms mdulos a esa librera como: TweetParser o
EmailParser.
El archivo de configuracin
Veamos un breve ejemplo de cmo sera 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 cdigo de nuestra aplicacin 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 configuracin que hemos
puesto en el apartado production y, si ponemos development o cualquier cosa que no tengamos
contemplada, veremos la opcin de development.
Recuerda
Las variables de entornos y NODE_ENV en concreto fueron tratadas en el captulo anterior.
Quiz quieras echar nuevamente un vistazo a esa seccin.
Para compartir los datos de la configuracin con las diferentes partes de la aplicacin que la necesitan
tenemos varias formas:
Usando una variable global
Si has ledo algn buen artculo sobre JavaScript en el pasado, sabrs que no es un buen patrn 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 podramos considerar el objeto window
de Node.js. As que podramos hacer algo as:
1 global.config = require('config');
Y podramos acceder a su valor desde cualquier punto de la aplicacin. 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 mdulo que queramos. De esta forma estaremos haciendo algo ms correcto.
Organizando el cdigo de nuestra aplicacin 52
Pasando parmetros
A mi modo de ver, no tiene mucho sentido que, si creas un mdulo para leer correos, el mdulo
sea capaz por s mismo de decidir a qu cuenta de correo ha de conectarse leyendo un archivo
de configuracin. Esto empeora notablemente la capacidad que tiene tu cdigo de ser reutilizable
(porque necesitaras un archivo con unos datos concretos).
Por ello, lo mejor es pasar los datos necesarios o bien al requerir el mdulo (como ya hemos visto)
o bien al crear una instancia de la clase que sea.
Emisin 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 rpido, ya que en vez de leer archivos o bases de datos con cada peticin, 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 ests 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
botn, cuando pulsamos una tecla, etc.
Los eventos forman parte del ncleo de Node.js y vamos a intentar ver cmo los interpreta Node.
Antes, me gustara hablar un poco sobre el patrn del Observador, sobre el cual se basan los eventos.
Patrn del observador
Este patrn es un patrn de diseo, 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, reaccionarn de alguna manera ante ese anuncio.
Voy a poner un ejemplo que creo que todo el mundo podr entender. Imagina que viajas todas las
maanas en metro para ir al trabajo, quiz no tengas que imaginarlo porque sea as La compaa
de metro ofrece alertas de las lneas por si surge algn problema a travs de Twitter. Ese sera nuestro
sujeto y nosotros, que seguimos la cuenta de Twitter, los observadores.
De repente, recibimos una alerta de que la lnea que usamos normalmente est cerrada porque un
tren qued averiado en medio de una estacin. 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:
Emisin 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 vers que, automticamente, aparece la cadena Avisando al jefe. En nuestro caso,
canalDeTwitter es nuestro sujeto y la funcin que asociamos al evento retraso es un observador.
El mtodo on que nos ofrece la clase EventEmitter nos permite estar pendiente de los eventos. El
primer parmetro es el nombre del evento y segundo es la funcin que se ejecuta cuando el evento
ocurre.
Quiz ahora ests pensando, vaya, podra haber avisado al jefe con una funcin nada ms y eso sera
todo. Lo interesante es que podemos hacer tambin algo como esto:
1 canalDeTwitter.on('retraso', avisarAlJefe);
2 canalDeTwitter.on('retraso', avisarAPareja);
3 canalDeTwitter.on('retraso', guardarEnBD);
De manera que todo eso podra estar distribuido por cualquier punto de la aplicacin y todos se
ejecutaran 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 parmetros a los eventos
Vale, hasta ahora solo sabemos que ha habido un retraso pero qu lo ha causado? Cmo 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 compaa de metro'
9 });
Deberas recibir este mensaje: Avisando al jefe del aviso ""Un tren se ha estropeado"
enviado por "La compaa de metro". Como puedes ver, es realmente potente y permite a varias
partes de tu aplicacin estar atentas para reaccionar ante los eventos.
Emisin de eventos con EventEmitter 55
Dejando de escuchar eventos
En alguna ocasin 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 ms sobre los eventos, podemos retomar el cdigo 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:
Emisin 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 peticin emite un evento request y nos pasa los parmetros request
(que es la peticin) 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 introduccin 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 comn para todos y, smplemente, tendremos que hacer funcionar el siguiente
cdigo:
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 cdigo que veremos ms abajo
13
14 var metroMadrid = new ClienteTwitter('@metro_madrid'),
Emisin 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 sper-clase. Podramos 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 ms sencilla pero requiere que usemos otro mdulo, adems de
events y que cambiemos un poco la definicin. No obstante, el resultado es bastante ms 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 ocasin, si sabemos algo de ingls, la frase toma sentido ya que estamos diciendo que la
primera clase hereda de la segunda.
Emisin de eventos con EventEmitter 58
Un ejemplo real
Vale, hasta ahora lo que hemos hecho ha sido lanzar un evento rpidamente y ver la respuesta
rpidamente. Vamos a hacer un pequeo ejercicio para ir abriendo boca.
Lo que vamos a hacer es una peticin a una pgina web real: http://isitchristmas.com/. Esta
pgina nos dice si es Navidad y, en el momento en que estoy escribiendo esto, no lo es. He elegido
esta pgina porque no tiene mucho misterio en el cdigo 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, vers que en pantalla aparece lo mismo que si abres la pgina y revisas el
cdigo 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 parmetros. En nuestro
caso la pgina que queremos obtener, el puerto (80 por defecto) y el mtodo, 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 parmetro
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 asncrona y la vaya parseando por trozos. Cada vez que un trozo est
disponible, emite un evento data que aprovechamos para pasarlo a la consola.
Emisin 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 peticin y, eventualmente,
se dispararn los eventos.
Como ves, es realmente sencillo y es algo que est presente en muchos de los mdulos 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 informtica, 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 estn construidas en muchos de los mdulos de No-
de.js. Cuando vimos la introduccin a Node.js, nos presentaban la herramienta como especialmente
creada para modelos de Entrada/Salida de datos. Esto suele ser la parte ms lenta de una aplicacin.
Node.js nos facilita un poco la tarea usando abstraccin en estos casos as que nos d igual si estamos
viendo bases de datos, ficheros de texto, etc.
Atencin!
Los Streams han cambiado mucho en Node.js desde que se salt de la versin 0.8 a
la 0.10 y ahora son conocidos como streams2. En el libro explicaremos cmo usar las
caractersticas 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 difcil comprenderlo puesto que los Streams son, bsicamente,
pipes (tuberas o cadenas de procesos). Hay 5 tipos bsicos de Streams que puedes crear: Readable
(lectura), Writable (escritura), Duplex (ambos), Transform (transformacin 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 seccin 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 cmo usarlos.
Los Streams en Node.js 61
La funcin pipe
La funcin pipe() nos permite mandar lo que pase en el Stream hacia otro sitio y que se encargue
de gestionar sus eventos. Esa funcin se llama sobre un Stream de lectura pasando como parmetro
uno de escritura y nos devuelve este ltimo. Para graficarlo:
1 origen.pipe(destino);
Si destino resulta que tambin es de lectura, podramos volver a encadenar:
1 origen.pipe(destino).pipe(destinoFinal);
Adems, 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 ledos hasta
que el buffer se vace 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 estn
leyendo. Si has ledo hace poco el captulo sobre los eventos, el objeto respuesta que obtenemos al
hacer una peticin 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 mtodo _ read(tamao). El tamao es algo recomendable y muchas de las
implementaciones pueden ignorarlo directamente. Cuando el Stream se hace con datos, los enva
llamando a this.push(trozo). Puede que te ests preguntando a dnde los enva? Pues, al otro
lado de la tubera. La tarea del Stream es solo una, en este caso la de leer datos, si al otro lado de
la tubera 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 parmetro un array. Primero nos aseguramos que
guardamos el valor de this en la variable self. Esto nos servir ms adelante ya que cambiaremos
el valor de this dentro del forEach.
Sobre esta tcnica
Esta tcnica es conocida en JavaScript como closure y es una de las herramientas ms
potentes que tiene el lenguaje. Si no ests muy familiarizado con el uso de esta tcnica,
te recomiendo que leas este artculo que explica cmo funciona el alcance (scope) de
las funciones en JavaScript e introduce la tcnica de las closures.
La siguiente lnea 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 haramos 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 opcin objectMode que, si es true, indica que
el Stream debe comportarse como un flujo de objetos y no un buffer. En la prctica, casi siempre
querremos esto ya que indica que stream.read(n) devuelve un nico valor y no un Buffer de tamao
n.
La funcin _read tan solo se encarga de recorrer la matriz con la funcin forEach y va aadiendo los
datos al Stream a travs de push con un retorno de carro al final para que la salida sea ms bonita.
Si os fijis, hemos ignorado el valor de tamano ya que es una recomendacin, no una obligacin.
Cuando terminamos el bucle, hacemos push(null). Esto le indica al Stream que hemos llegado al
final y que ya no va a recibir ms datos por lo que est listo para cerrarse.
Luego hacemos que la clase que hemos creado, MiStreamDeLectura, herede de Stream.Readable.
Adems, creamos una matriz de relleno que nos sirve para ilustrar el ejemplo.
Finalmente, usando la funcin pipe, redirigimos los datos del Stream hacia process.stdout que no
es ms que el terminal y lo cual permite que veamos el resultado por la pantalla.
Si lo ejecutamos, vers 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 mtodo _ write(trozo, codificacion, callback). El trozo, no es ms que
Los Streams en Node.js 64
la porcin de datos que ha sido leda. La codificacion se usa en el caso de que el trozo sea una
cadena, para codificarla claro La codificacin puede ser utf8, utf16le, ucs2, ascii o hex. Aunque
normalmente no tendremos que tocarla, lo ms habitual ser utilizar utf8 en caso de que vayamos
a escribir caracteres especiales.
Figura 5: Eventos del Stream de Escritura
El callback es la funcin que se ejecutar (con un argumento inicial de error) cuando hayamos
terminado de procesar nuestro trozo. Ten en cuenta que esta funcin no debe ser llamada por el
cdigo directamente y la usar la clase de manera interna.
Y ahora te estars preguntando si no puedo llamar yo a _write cmo escribo datos? Con la
funcin write, que recibe los mismos parmetros que la funcin _write siendo opcionales los dos
ltimos. Esta funcin nos devuelve true o false. true significa que todo va bien y que podemos
seguir mandando datos todava. false significa que el buffer est lleno y que por tanto, no puede
transportar ms datos. En este caso los datos sern enviados en breve. Un ejemplo muy grfico 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 ms cajas, sigamos trayendo ms (true). Llega
un punto en que la furgoneta se llena y te avisa: Para de traer cajas que no caben ms! 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 tambin el mtodo end() que simplemente indica que el Stream
ya ha terminado de escribir. Este mtodo acepta los mismos parmetros que si llamramos a write,
permitindonos escribir algo de cierre en el Stream. La llamada a esta funcin emite un evento
finish que nos permite saber que el Stream ha terminado su trabajo.
Vamos a ver cmo 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 implementacin de este Stream es bastante trivial ya que nicamente
guardamos el valor en memoria, pero podra 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 vaca 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 implementacin de _write no esconde complejidad alguna ya que nicamente vamos encade-
nando el trozo que convertimos a cadena con la funcin toString a valor. En nuestro caso, como
ya hemos almacenado el dato, llamamos a callback (por si la hubiera), sin pasar ningn error ni
parmetro ya que la implementacin es bastante simple.
Despus asociamos una funcin 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 StreamDuplex, ha de heredar de la clase stream.Duplex e implementar
los mtodos _ write(trozo, codificacion, callback) y _ read(tamao) que ya hemos visto.
Transformacin - Transform
Este tipo de Streams nos sirven para transformar datos: Entran datos y salen datos ligeramente
transformados. Se considera buena prctica 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 sera tener 3 Streams:
1. El primero de lectura, en el que entran los datos
2. El segundo, de transformacin, 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 mtodo _transform(trozo, codificacion, callback) que, como ves, es
esencialmente el mtodo _write de los Streams Writable.
Vamos a ver cmo implementamos este tipo de Stream para hacer que al pasarle una matriz de
nmeros, nos devuelva la ensima potencia del nmero:
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 implementacin es realmente sencilla.
Lo primero que vers es que vuelve objectMode: true tal y como lo usamos en el Stream Readable.
La funcin _transform lo primero que hace es convertir el trozo en un entero con la funcin
parseInt. Luego, calcula la potencia y la vuelve a convertir a cadena almacenndola valindonos
de push. Esto es porque los Streams solo manejan cadenas de caracteres ( o buffers ).
Si ejecutas este script, vers 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 perodo 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 dramtico al convertirlas en tareas asncronas.
Recuerda
Si quieres saber ms sobre la asincrona en Node.js recuerda visitar el primer Captulo.
Las funciones que vamos a explicar aqu, tienen una versin sncrona que no vamos a ver. Usar este
tipo de funciones va contra la naturaleza de Node.js pero ah estn! Tan solo hay que aadir Sync
al nombre de la funcin para que esta sea sncrona.
Las operaciones que vamos a ver son las de lectura y escritura principalmente pero hay muchas ms
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
documentacin.
Leyendo ficheros
Antes de seguir, y si ests siguiendo los ejemplos junto a tu ordenador (cosa que te recomiendo),
necesito que crees un fichero con extensin txt en la raz de tu aplicacin, 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 funcin recibe la ruta/nombre del archivo, un objeto (opcional) de configuracin, y una funcin
que se llamar cuando haya ledo el archivo.
Si estis 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
parmetro 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 funcin recibe el nombre/ruta del archivo, los datos a escribir, un objeto opcional de
configuracin, y una funcin que ejecutar cuando haya escrito. Esta vez, Node.js se encarga de
establecer la codificacin a utf8 por defecto. No podra haberlo hecho antes?
Si ejecutas la aplicacin y abres la carpeta, vers que se ha creado un archivo homer.txt y que dentro
tiene el contenido que le hemos pasado.
Esta funcin reemplaza el contenido del archivo por el que le hayamos pasado. Si lo que queremos
es aadir al archivo, tenemos la funcin fs.appendFile que recibe los mismos parmetros pero
aade al contenido al final en vez de sobrescribir.
Los Streams y los ficheros
Cuando hemos hablado de los Streams, hemos comentado que los Streams estn bien dentro de
Node.js. Para demostrarlo, vamos a ver cmo 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 funcin acepta un segundo parmetro que es un objeto de configuracin al que podemos
cambiarle la codificacin (entre otras cosas) y especificar los bytes de inicio y fin con los parmetros
end y start. Estos dos ltimos parmetros pueden ser interesantes ya que algunos archivos (como
los de vdeo), almacenan informacin en los ltimos caracteres del archivo.
El parmetro encoding no lo vamos a tocar en esta ocasin (ahora veremos por qu).
Y bien, ahora tenemos una manguera que est empezando a bombear agua dnde 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 anlogo, y lo hemos enchufado al Stream de Lectura. Dado que ambos Streams
tratan con buffers, no necesitamos codificar la informacin ya que va directamente a un archivo
copiando los datos de origen al destino.
Si abres ahora el archivo homer.txt, vers que el contenido es completamente idntico al que hay
en prueba.txt. A qu es fcil?!
Resumen
En este, corto pero intenso, captulo hemos aprendido algunos de los conceptos fundamentales de
Node.js que nos servirn en nuestro da a da en el desarrollo de nuestras aplicaciones.
El primer tema tratado ha sido la gestin de dependencias con Node.js a travs del archivo
package.json. Hemos aprovechado para ver cmo funciona el versionado semntico (y como ste
es importante en Node.js) y cmo instalar dependencias para desarrollo.
Luego, hemos visto la exportacin 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 mtodos, las particularidades que pueden tener cada una de ellas as
como elegir el mtodo adecuado para cada ocasin. Finalmente, hemos visto cmo podemos pasar
argumentos a la hora de importar algo que estamos exportando.
A continuacin, hemos visto cmo podemos organizar nuestra aplicacin Node.js. Teniendo en
cuenta que es siempre algo subjetivo, he intentado ofrecer una visin que pudiera servir y fuera
lgica para la mayora de los programadores.
Adems, hemos tratado la emisin de eventos con EventEmitter, hemos comprendido (esperemos)
el patrn del Observador y cmo ste es implementado por Node.js. Adems, hemos creado nuestros
propios eventos y pasado argumentos a stos para dotarlos de ms contexto. Luego, nos hemos puesto
manos a la obra y descubierto cmo podemos crear nuestra propia clase emisora de eventos para lo
cual hemos indagado un poco en la herencia de clases en JavaScript y cmo lograrla ms fcilmente
con Node.js. Finalmente, hemos creado una pequea aplicacin que nos descarga una pgina web y
va emitiendo datos de sta, aplicando los conocimientos adquiridos.
En siguiente lugar, nos hemos encargado de los Streams, centrndonos en la especificacin conocida
como streams2. Hemos intentado descifrar qu es exactamente un script y cmo funciona la funcin
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 cmo podemos leer
ficheros y escribirlos. Adems, hemos visto cmo podemos aplicar lo aprendido con los Streams al
sistema de ficheros.
En el siguiente captulo comenzaremos ya a trabajar con Express, descubriendo paso a paso cmo
podemos crear nuestra aplicacin y qu podemos hacer con ella en cada caso.
Introduccin a Express
Ahora que ya tenemos unas nociones bsicas de cmo funciona Node.js y de para qu sirve, estamos
preparados para dar el siguiente paso en nuestro particular camino de iluminacin. 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 odas pero no los conoces realmente. Vamos a
intentar cambiar eso.
Express es un framework que te ayudar a organizar tu aplicacin, 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 algn casual
lo habis usado alguna vez, su uso resultar natural.
La caracterstica ms potente de Express (a mi modo de verlo) es la versatilidad que te ofrece a la
hora de gestionar las rutas de nuestra aplicacin.
Otros frameworks de Node.js
No pensarais que solo tenemos un Framework en Node.js, no? La verdad es que hay muchos y no
quera 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 estn basados en Express.
No obstante, esto tiene un precio: Tienes que desarrollar tu propio cdigo para ciertas cosas.
Meteor
Web - Github
Es uno de los chicos ms famosos del lugar. Lograron una financiacin 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 prcticas ni los estndares establecidos por la comunidad, ni tampoco REST. Eso
implica que no puedes usar cualquier mdulo que haya en el mercado con Meteor ya que tienes que
instalarlo con su propio sistema de paquetes y mdulos.
Derby
Web - Github
Una de las cosas en las que Derby presta ms atencin es en la de permitirnos usar el mismo cdigo 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 produccin
flatiron
Web - Github
Flatiron intenta encapsular componentes con caractersticas concretas para usar, permitiendo a los
desarrolladores poner o quitar lo que les interesa. Flatiron est adems desarrollado por Nodejitsu,
una de las compaas 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. Adems su documentacin es bastante buena.
La pega? En primer lugar, usa CoffeeScript, con lo que ya aade algo ms 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 convencin sobre configuracin, 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 da 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. Estn ya por la versin 3.2.0 y han ido mejorando
bastante con el tiempo. Cierto es que hay que realizar ms trabajo al inicio, pero yo prefiero ser
consciente de lo que funciona y cmo funciona a tener una especie de vodoo que se encargue de
hacer cosas.
Para mi gusto, lo nico a criticar de Express es su documentacin: tanto Express como Jade podran
estar mucho mejor documentados en mi opinin.
Una pequea aclaracin
A lo largo del captulo voy a intentar explicar las partes ms oscuras de la documentacin de Express
ofreciendo ejemplos y hablando de ellos y tratar las partes ms interesantes. Para todo lo dems,
no est MasterCard, pero tenis la documentacin de Express (que deja mucho que desear en mi
opinin) en esta direccin http://expressjs.com/api.html
Instalacin 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 rpidamente con algo de cdigo ya dentro.
As que, vamos a instalarla de forma global
1 npm install -g express
Creando la estructura bsica
Lo primero que tenemos que hacer es dirigirnos a un directorio en donde queramos crear nuestra
aplicacin y luego escribir lo siguiente:
1 express miaplicacion
Si el directorio ya existe y no est vaco, nos avisar. Obviamente, podemos cambiar miaplicacion
por lo que queramos. Esto crea la estructura bsica de carpetas recomendada por Express y con los
siguientes mdulos:
Sistema de plantillas: Jade (lo veremos ms adelante)
Sistema de CSS: CSS plano
No obstante, podemos aadir ms cosas aadiendo parmetros 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 instalacin en un directorio que no est vaco
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 bsica 79
Express se ha encargado de crearnos su propio archivo package.json con las dependencias que
necesita y unas carpetas para sugerirnos cmo organizar la aplicacin. Veamos cada una de las
carpetas:
public : Express diferencia el cdigo que se muestra en el navegador del de la aplicacin del
servidor. Todo lo que est en esta carpeta ser accesible por el usuario que visite tu aplicacin.
images, javascripts, stylesheets : Creo que os podis hacer una idea de lo que se
supone que va aqu. No obstante, me gustara sealar que yo prefiero renombrarlas a
img, js y css ya que es la convencin ms habitual hoy en da.
routes : Aunque entraremos en este tema en breve, cada url de nuestra aplicacin 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 estn escritas en este pseudo-lenguaje que
veremos ms adelante.
Welcome to Express - Bienvenido a
Express
Ahora hagamos un ejercicio. No abras el archivo app.js todava. Smplemente entra en la carpeta
desde el terminal y escribe lo siguiente:
1 node app.js
Rpidamente vers aparecer en pantalla:
1 Express server listening on port 3000
Abre tu navegador favorito y abre la direccin http://localhost:3000. Si todo ha ido bien, deberas
poder leer el mensaje que encabeza la seccin:
Welcome to Express
Has visto qu fcil? Ya tenemos un servidor de Express corriendo en el puerto 3000 y que nos da la
bienvenida. Ahora s, te dejo abrir el cdigo 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 ms detalle a lo largo del captulo 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
mdulo http y el mdulo path.
Lo siguiente que hacemos es crear una instancia de Express que guardamos en app.
A continuacin, establecemos ciertos parmetros de la configuracin de Express. En orden de
aparicin:
1. El puerto en el que correr la aplicacin. Si establecemos una variable de entorno PORT con
algn valor lo usar, de lo contrario caer al 3000.
2. El directorio en el que estn 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 peticin 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 aplicacin.
9. Indicamos que todo el contenido de public es servido.
Luego, si estamos en desarrollo, aadimos 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 aplicacin 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 lneas de cdigo (que ni siquiera hemos tenido que escribir), para crear un
servidor web que responde a nuestras peticiones rpidamente.
Ahora vamos a seguir indagando y viendo para qu sirve cada cosa con ms profundidad.
Configuracin de la aplicacin
Cuando obtenemos la variable de la aplicacin de express, a travs de la funcin express(), podemos
pasar a configurarla con profundidad con ciertos mtodos que esta expone. Para todos estos ejemplos
de cdigo, supongamos que tenemos este cdigo al principio de cada uno:
1 var express = require('express');
2 var app = express();
Guardando y obteniendo valores en la aplicacin
Express nos permite guardar valores dentro de la aplicacin. Podemos por ejemplo guardar la versin
que tenemos para mostrarla en algn 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! Adems tenemos otros 3 mtodos
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
Configuracin de la aplicacin 84
que como ves, son atajos para ahorrarnos algo de texto.
El caso es que Express tiene algunos valores secretos que nos sirven adems para modificar el valor
de la aplicacin:
view - Define el objeto de la vista. ste no debemos sobreescribirlo bajo ningn concepto.
Valores
views - Define dnde se encuentra la carpetas donde estarn las vistas almacenadas. Por
defecto es la carpeta views en la raz de tu aplicacin.
view engine - Define el motor de vistas que vamos a usar.
jsonp callback name - Define el nombre de la funcin que se usa en respuestas JSONP
por defecto es callback.
json spaces - Define el espacio de tabulacin 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 nmero de partes que tiene tu dominio. Por defecto es dos
porque la mayora de aplicaciones corren en dominios del tipo ejemplo.com pero cambia
si usamos algo como ejemplo.co.uk (malditos britnicos)
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 maysculas o minsculas
en las rutas. Por defecto est desactivado por lo que /usuario y /UsUaRiO son idnticos.
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 aplicacin
Express es muy amigo de facilitarnos el trabajo y nos ofrece algunas formas de ahorrarnos caracteres.
La funcin configure es uno de esos ejemplos. Esta funcin nos permite configurar algn parmetro
dependiendo del entorno en el que nos encontremos. Vemoslo con un ejemplo:
Configuracin de la aplicacin 85
1 // Todos
2 app.configure(function(){
3 app.set('titulo', 'Mi aplicacin');
4 });
5
6 // Solo desarrollo
7 app.configure('development', function(){
8 app.set('bdatos', 'localhost');
9 });
10
11 // Solo produccin
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 parmetros 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 sern accesibles por todas las
plantillas de nuestra aplicacin. Muy til si queremos tener valores como el ttulo de la pgina o un
telfono o algo as. Para ello, solo tenemos que colocarlas en el objeto app.locals.
1 app.locals.titulo = 'Mi aplicacin';
2 app.locals.saludo = function(usuario) {
3 return "Hola " + usuario + "!";
4 }
Merece la pena destacar, que app.locals es tambin una funcin a la que podemos pasarle un objeto
que se encargar de unir a los datos que ya tenga. Por ejemplo:
Configuracin de la aplicacin 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 ms adelante.
Rutas
Ahh las rutas Las rutas son el autntico corazn de Express. Estoy seguro de que no tardars
mucho en comprenderlas ya que son bastante naturales de leer y entender.
Figura 6
Como ves es bastante sencillo. Pero vamos a profundizar ms. Estoy seguro de que al menos ests
acostumbrado a POST y a GET. Las otras dos, PUT y DELETE son parte de la especificacin 1.1 del
protocolo HTTP. Hay ms pero no se suelen usar: HEAD, TRACE, CONNECT y OPTIONS.
Por norma general, GET no debera provocar ningn cambio en los datos y es solo para obtener datos
mientras que POST, PUT y DELETE s que modifican (o deberan) datos.
Lo habitual en estos das es usar la filosofa REST. No voy a profundizar mucho en esta filosofa,
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 coleccin o crea un subordinado de un recurso.
PUT : Actualiza una coleccin de recursos o un recurso en particular.
DELETE : Borra un recurso o una lista de stos.
Despus del verbo viene la ruta que puede (o no) contener parmetros. Aunque stos los veremos
un poco ms adelante. Finalmente viene una funcin, una lista de ellas o incluso una matriz de
funciones. Esto quiere decir que todo este cdigo 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 parmetros y sirven para controlar lo que ocurre. Podemos
decidir enviar una respuesta al usuario, enviar un error, pasar el cdigo a la siguiente funcin de la
lista o no hacer nada. Los tres parmetros son los siguientes:
req : Del ingls request, que es la peticin. En ella encontraremos datos de la peticin tales
como las cabeceras que enva el navegador o los parmetros de la ruta si es que los hubiera.
res : Del ingls response, que es la respuesta. Si no mandamos ninguna respuesta a la peticin,
el navegador fallar porque lleva demasiado tiempo esperando respuesta o bien con un error
324 ERR_EMPTY_RESPONSE.
next : Del ingls ohh wait! Viene a ser la siguiente funcin a la que llamar. Cuando nuestra
ruta termine su trabajo le puede pasar el testigo a la siguiente. Si pasamos un parmetro 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 parmetros los veremos con mayor profundidad en un momento.
Veamos un sencillo ejemplo de ruta para ponernos en faena. Antes de seguir, me gustara indicar
que este es el cdigo que precede a cada ejemplo para evitar cdigo 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 fcilmente.
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 queramos! 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 peticin 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 podis descargar Postman Rest Client
que nos permitir crear cualquier tipo de peticin y mandarla a cualquier punto.
Parmetros
Como ya he dejado ver a lo largo del captulo, las rutas pueden contener parmetros. 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
Deberamos ver en el navegador: Me has mandado: 123. Con :[nombre del parmetro],
podemos definir cualquier parmetor en nuestra URL. Podramos incluso mezclarlos con estructuras
necesarias como:
1 /mensaje/:origen/a/:destino
Y quedarn disponibles dentro del objeto params que est dentro de req.
Adems, podemos definir opciones para lo que podemos recibir en el parmetro. 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 parmetro 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, vers que lo que aparece es: Me has
mandado: test. Esto es porque esa ruta tambin coincide con la ruta anteriormente definida y al
estar definida en primer lugar, entrar por sta en lugar de la ms especfica. Es muy importante
tener esto en cuenta a la hora de disear las rutas y el orden en que las pongamos en la aplicacin.
Parmetros opcionales
Es posible que, por el motivo que sea, queramos que un parmetro pueda ser opcional. Veamos cmo
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 interrogacin, el parmetro 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
Parmetro con expresin regular
Imagina que las IDs solo pueden ser numricas y no queremos que si no es completamente numrico,
se llame a la ruta. Con nuestro primer ejemplo: /usuario/:id lo mismo da que pongamos 123 o que
pongamos manolito. Cmo lo arreglamos? Con una pequea expresin regular:
1 app.get('/usuario/:id(\\d+)', function(req,res,next){
2 res.send('Me has mandado: ' + req.params.id );
3 });
Esta slo aceptar nmeros mientras que \\w+ solo aceptar una cadena de caracteres. Esta es la
forma fcil. 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 parmetros dado que no hay
nombres si no, ms bien, partes. Por ello, en vez de usar nombres, usamos nmeros de 0 a n. En
nuestro ejemplo, los datos estaran en el 0, ya que es el primer parmetro.
1 app.get('/^\/usuario(?:\/([^\/]+?))?\/?$/i', function(req,res,next){
2 res.send('Me has mandado: ' + req.params[0] );
3 });
Elemento comodn
En ocasiones puede que queramos responder a una ruta y a todo lo que venga tambin por detrs.
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 parmetros ya que pueden fastidiarte una ruta si
la pones al principio del todo. Y cmo accedemos a lo que viene detrs? 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 bsqueda
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 parmetros de bsqueda.
Imaginemos que queremos ofrecer libros por categora. Podramos usar esta URL:
/libros?categoria=terror
Y quiz queramos aadir el autor tambin:
/libros?categoria=terror&autor=stephen+king
Cmo 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?
Tcnicamente 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 aadir informacin a la peticin, terminarla
o pasar a la siguiente funcin 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 peticin.
Veamos un ejemplo que yo creo que todos entenderis 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 envo 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 preocupis, ya lo tengo yo! Es por eso que Express, por defecto, coloca el router
antes del Middleware que sirve los archivos estticos. Para evitar que un nombre desafortunado
sobreescriba tu ruta.
Ahora que esto nos ha quedado ms o menos claro, veamos cmo usar los Middleware.
app.use
Este es el ejemplo ms bsico de uso de MiddleWare y, lo que hace es aadir una funcin a la pila
de Middlewares. Ten en cuenta que usando app.use todas las rutas pasarn por ese Middleware.
Veamos un ejemplo de Middleware para evitar el acceso a nuestra aplicacin 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 pequeo Middleware revisa las cabeceras que enva el navegador y que estn en el objeto
req para ver si coincide con alguna de las de Internet Explorer y, si es as, usa el mtodo end() para
finalizar la peticin, indicando que no es un navegador compatible. Como ves, lo colocamos antes
del router y del gestor de archivos estticos, 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 aplicacin (que es
precisamente lo que hace express.loger), pues este es el mejor punto.
En lnea
Otra forma que tenemos de pasar Middlewares es individualmente a cada ruta. Supongamos que
tenemos una funcin que valida si un usuario est o no logado en nuestra aplicacin 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 funcin adminLogado, se ejecutar antes de ejecutar la funcin final y, por tanto,
todas las rutas quedan efectivamente protegidas por adminLogado.
Otra forma de hacerlo sera 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 funcin que le toque,
en funcin de la ruta a la que estemos llamando.
Middlewares 98
Mapeado
Express nos permite escuchar cuando estamos pidiendo algn elemento en una ruta. Imagina que,
cuando ests entrando a una ruta con :id_usuario pudieras llamar automticamente a una funcin
que trajera al usuario de la base de datos y pasarlo con los datos de la peticin. 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 algn ORM que se encargue de
hacer las operaciones de lectura, actualizacin y borrado de una base de datos. Al colocar este cdigo,
le estamos diciendo a Express:
Cada vez que en una ruta est id_usuario, tretelo de la base de datos y me lo dejas en
la peticin.
No es conveniente?
En resumen
Vamos a intentar resumir cundo es bueno usar cada mtodo:
app.use : Usaremos este mtodo cuando queramos que todas las rutas sean filtradas por este
Middleware.
En lnea : Usaremos este mtodo cuando queramos que ciertas rutas sean filtradas.
Mapeado : Usaremos este mtodo cuando queramos que al usar un parmetro especfico en
la ruta, ste sea tratado por la aplicacin y asociado as a la peticin.
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 ms
repasarlos todos para ver lo que nos pueden ofrecer.
basicAuth
Este Middleware nos ofrece una proteccin bsica de usuario y contrasea 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', 'contrasea'));
En el caso de que queramos algo un poco ms complejo y consultemos el usuario y la contrasea en
la base de datos, seguramente estemos hablando de asincrona, 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 funcin callback para que una vez nos traigamos los datos de la base
de datos, podamos invocarla. A la funcin 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
aplicacin. Para que nos entendamos, es el encargado de que una peticin POST coloque los
parmetros en req.params o nos facilite los archivos de haberlos.
1 app.use(express.bodyParser());
compress
Si os habis movido por Internet y conocis algunas libreras de JavaScript, como Ember, veris
que pone algo as como min + gzip. compress se encarga de comprimir los datos enviados para que
las comunicaciones sean ms 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 mayora 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 aadiendo 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 aadir soporte para sesiones en
Express. Si algn Middleware hace uso de los valores que almacenes en sesin, ten en cuenta que
debers colocarlos bajo la declaracin de este Middleware.
1 app.use(express.session());
cookieSession
cookieSession nos permite tener cookies basadas en la sesin del usuario y se encarga de rellenar
req.session con esa informacin. ste puede recibir 4 parmetros:
key : Indicador de la cookie. Por defecto es connect.sess
secret : Evita la alteracin de cookies por externos al aadir una palabra secreta.
cookie : Cambia la configuracin de la cookie que por defecto es:
path : /
httpOnly : true
maxAge : null
proxy : Confa en el proxy inverso al establecer cookies seguras.
1 app.use(express.cookieSession());
Dado que las Cookies se guardarn en sesin, podremos deshacernos de ellas estableciendo
req.session a null antes de enviar la respuesta.
Middlewares 101
csrf
Este Middleware nos ofrece proteccin contra Cross-site request forgery (falsificacin de peticin
en sitios cruzados).
Este Middleware se encarga de generar un token llamado _csrf que debemos aadir 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 sesin por lo que tendremos que colocarlo bajo el uso de
session().
static
Este Middleware es de los ms bsicos de Express y, como ya sabes, se encarga de servir el contenido
esttico 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. Bsicamente lo que hace es que si tenemos por
ejemplo el Middleware:
1 app.use(express.static(__dirname + '/public'));
Ytenemos la carpeta /public/imagenes/portal, podremos ver sin problemas la imagen /public/imagenes/portal/cake.png
pero si accedemos directamente al directorio, obtendremos un error.
Para subsanarlo, tenemos que colocar este Middleware antes del esttico:
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 parmetro (que ha de ser una funcin) a los archivos.
Por defecto es false.
La peticin - request
La gran mayora de las cosas interesantes sobre la peticin, ya las hemos visto cuando hemos hablado
de las rutas. No obstante, voy a detenerme en alguno de los mtodos 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 peticin del tipo que sea al servidor, gracias al uso de bodyParser, todos los
parmetros 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">Contrasea</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 mtodo 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 mtodo para acceder a cualquier parmetro. Hasta ahora, hemos visto que
tenemos 3 tipos de parmetros (lamento desilusionarte, no hay ms):
req.params : Los que van dentro de la definicin 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 peticin
http://expressjs.com/api.html#request
La peticin - 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 funcin a no ser que sea totalmente claro
ya que puede prestar a confusin.
req.is(tipo)
Este mtodo nos permite comprobar el tipo de datos que nos llega en la peticin. Imagina que quieres
comprobar si la peticin enva datos del tipo JSON:
Normalmente, el tipo del contenido enviado al hacer esta peticin, sera application/json.
Express nos permite comprobarlo con esta funcin:
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 aplicacin a ltima IP desde la que se ha logado un usuario por
ejemplo, o mantener una lista de IPs conectadas a nuestra aplicacin.
Express se encarga de guardar la IP de la peticin directamente en la propiedad ip dentro del objeto
req.
req.xhr
Esta propiedad es bastante til ya que nos permite saber si es una peticin del tipo AJAX.
Bsicamente, cuando usamos alguna funcin AJAX con jQuery por ejemplo, se enva la cabecera
X-Requested-With con el valor XMLHttpRequest.
Si revisamos el valor de req.xhr ser true si es una peticin 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 (fcilmente claro).
La respuesta - response
Cuando modificamos el objeto res, estaremos modificando lo que se va a enviar de vuelta cuando
se recibe una peticin. Por ello, has de tener especial cuidado ya que si bien los datos de req son ms
de consulta, los de res se envan de vuelta al usuario (normalmente).
res.status
Esta funcin 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 ms habituales:
Cdigo Descripcin
200 Respuesta estndar para peticiones correctas
301 La URL ha cambiado. Inicia redireccin normalmente
401 No autorizado, posiblemente haya que iniciar sesin
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 peticin se enve si no que has establecido
que esa es la cabecera que recibe el navegador.
res.redirect
Con este mtodo podemos realizar una redireccin. La redireccin puede ser a una URL completa
(como por ejemplo google) o a una ruta en nuestra aplicacin. Este mtodo s que termina la respuesta
y, si la redireccin es a nuestra aplicacin, 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 funcin que se encarga realmente de enviar una respuesta. Adems, para facilitarnos la
tarea, incluso podemos establecer un cdigo 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 nmero, 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. Adems, podemos valernos de res.json para pasar datos como null o undefined que no
son tcnicamente vlidos como JSON.
No obstante, qu pasa si queremos enviar JSONP?
Pues que tendremos que valernos de esta funcin:
1 res.jsonp({ fecha : new Date() });
Recuerda que la funcin usada por defecto es callback y que para cambiarla tendremos que usar el
ajuste de la aplicacin:
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
artculo en Funcin 13 sobre las peticiones Ajax Cross-Domain.
res.sendfile
Si queremos enviar un archivo al navegador para visualizarlo, esta es la funcin 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 enva
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 funcin adems acepta un segundo parme-
tro en el que le podemos indicar el nombre que queremos usar para la descarga, pudiendo as darle
un nombre ms bonito.
res.render
Esta funcin 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 funcin.
1 res.render('miPlantillaDeFechaYHora', { fecha: new Date() });
La funcin recibe el nombre de la vista a mostrar, un objeto con parmetros para la vista, y una
funcin de callback siendo los dos ltimos parmetros 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 algn momento pero Jade es el sistema de plantillas por defecto de
Express. Seguramente te ests preguntando qu es una plantilla y para qu sirven. Vamos a verlo!
Las plantillas te permiten escribir el cdigo mnimo necesario para que una pgina muestre HTML.
Los motores de plantillas nos permiten hacer cosas chulas como anidar plantillas, ejecutar cdigo
en ellas, etc.
Para empezar con Jade, antes qtengo que hacer una advertencia. Jade permite la indentacin con
tabs o con espacios pero, uses la que uses, no puedes mezclarla con la otra. Adems, las plantillas de
Jade, tienen extensin .jade.
Lo primero que vamos a hacer es echarle un vistazo al archivo layout.jade que Express cre por
nosotros cuando creamos nuestra aplicacin 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 estn al mismo nivel. Esto quiere decir que estn 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 parmetro
a la hora de renderizar una ruta. Recordemos:
1 exports.index = function(req, res){
2 res.render('index', {title : 'Express'});
3 };
Aprovecho tambin para recordar, que podramos borrar ese objeto y ponerlo como parmetro local
de la aplicacin:
Plantillas con Jade 109
1 app.locals.title = 'Express';
Si pasamos adems un parmetro title igualmente, este sobreescribir al valor local de la aplicacin.
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 ms corto. Lo primero que destacamos es que dice extends layout y aunque
seguro que te lo ests imaginando, s: significa que extiende a layout.jade.
El bloque content vuelve a aparecer, para indicar al motor dnde debe colocar el contenido. As
pues, el resto del archivo pende del bloque content.
Una vez ms, vemos un elemento h1 cuyo valor es el de la variable title y en el prrafo volvemos
a usarlo.
Sintaxis bsica
Jade fue creado por el mismo programador que Express as que su documentacin es igual de buena.
No obstante, he de reconocer que ha ido mejorando bastante con el tiempo.
La documentacin, y la pgina web oficial de Jade es esta: http://jade-lang.com/
Vamos a ver qu podemos hacer.
Creacin de nodos
En Jade podemos crear un nodo simplemente poniendo la palabra. Si queremos crear un elemento
h1 pues ponemos smplemente:
1 h1 Soy un gran ttulo
2 // Se traduce en <h1>Soy un gran ttulo</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 automticamente las etiquetas de apertura y cierre por nosotros. Como
norma general, cada lnea de Jade comienza con el nombre de un elemento y un espacio y cualquier
texto o atributos que aadir al atributo.
Como ya hemos visto, si tabulamos, los elementos se meten dentro del padre.
Aadiendo atributos a los nodos
La regla dice que para aadir un atributo a un nodo, simplemente lo metemos entre parntesis 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 ests 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 parntesis:
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 aadir ms 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 salindonos 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 ms bsico.
Plantillas con Jade 111
1 #mi-div
2 // Se traduce en <div id="mi-div"></div>
3 ~~~~~~
4
5 ### Aadiendo texto
6
7 Para aadir 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 IpsumDolor // Se traduce en <p>Vehicula Tristique IpsumDolor</p>
Pero qu pasa si queremos aadir 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 viva un hidalgo
5 | de los de lanza en astillero, adarga antigua,
6 | rocn 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 viva un hidalgo de los de lanza en astillero, adarga antigua,\
10 rocn 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 opcin:
1 p.
2 En un lugar de la Mancha,
3 de cuyo nombre no quiero acordarme,
4 no ha mucho tiempo que viva un hidalgo
5 de los de lanza en astillero, adarga antigua,
6 rocn 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 viva un hidalgo de los de lanza en astillero, adarga antigua,\
10 rocn flaco y galgo corredor.</p>
Al poner un punto justo despus 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 inters ahora mismo pero cmo pondras 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 mtodos ya que solo puede ir texto
dentro de ellos.
Anidando elementos
Ya hemos visto la anidacin bsica 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 pequea 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 ests creando un formulario de edicin
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. Tambin 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 + tambin para concatenar cadenas, como lo haramos en
JavaScript.
Variables opcionales
Una de las cosas que uno descubre ms tarde o ms temprano, es que si intentas renderizar una
plantilla con una variable que no ests 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 vacas. Para ello, vamos
a valernos de un mdulo que se llama xtend y que extiende los objetos. De la misma forma que
lo hace $.extendde jQuery. Es decir, si dos (o ms) objetos tienen propiedades idnticas, la que se
preserva es la del objeto ms a la derecha. Veamos un rpido 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 Matas',
13 correo : 'sombragriselros@gmail.com'
14 };
15
16 console.log(extend(usuarioDefecto,usuario));
17
18 // { nombre: 'Antonio',
19 // apellidos: 'Laguna Matas',
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 (vaco). 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 cmo
hacerlo:
1 if locals.movil
2 p
3 strong Mvil
4 | #{movil}
5 else
6 p
7 strong Mvil
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 sper objeto locals por lo que, de esta forma,
podemos evaluar si la variable est o no definida.
Plantillas con Jade 115
Bloques de cdigo auxiliares
Jade nos ofrece algunos bloques de cdigo 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.
Adems de if, Jade nos ofrece el bloque unless (a menos qu) que es equivalente a if (!
(expresin)):
1 unless usuario.edad > 18
2 p No puedes ver aun esta pgina
case
El bloque case es exactamente lo que esperas que sea, evala una variable y muestra algo en funcin
del valor de la variable. Vemoslo con un ejemplo sacado directamente de la documentacin.
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.
Iteracin 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 cmo 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 instruccin each podemos iterar sobre matrices y objetos fcilmente. Incluso podemos
obtener el valor de la propiedad y/o el ndice del elemento en la matriz como acabamos de ver,
aadiendo una , y otra palabra para indicarlo. Si each no te gusta, puedes intercambiarlo por for
que hace lo mismo.
Aade contenido a un bloque
Os acordis 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, ests en la plantilla de registro, y quieres incluir en una de esas libreras tan chachis para
validar formulario y necesitas incluir una plantilla adicional. Las aades para todas? Es una opcin.
Pero tambin podemos aadir contenido a esos bloques:
1 extends layout
2
3 block append head
4 style(src='/validador-molon.css')
Con append aadimos el contenido que metamos al final del bloque. Si usamos prepend por el
contrario, aadiremos contenido al principio del bloque.
Pginas de error en Express
Ahora que ya tenemos un poco de conocimiento sobre Jade, podemos crear unas pginas personali-
zadas para un error 404 o 500 (que suelen ser los ms 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.
Adelos justo despus 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 pgina de inicio
Pginas 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, cmo funcionan esos dos Middlewares?
Cuando Express no encuentra a ninguna ruta a la que pasarle la peticin dentro de app.route,
comienza a seguir por todos los Middlewares que hayamos definido por debajo. Normalmente
buscar primero si puede servir algn archivo esttico con app.use(express.static . Si tampoco
lo consigue, entrarn 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 parmetro.
Por qu ponemos como error 400? El error 400 significa que la peticin ha sido errnea. Algunos
navegadores muestran una pgina 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 pgina 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.
Gestin de Login con Passport
Una de las cosas ms habituales que implementamos en la mayora de las aplicaciones, es un poco
de seguridad. Y con ello me quiero referir a Login principalmente. En la mayora de las ocasiones,
tendremos partes de una aplicacin que no queremos que sean accesibles a no ser que se haya
iniciado sesin.
Passport es un genial Middleware para Express que nos ayudar a implementar un sistema de
autenticacin 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
Adems, para nuestro pequeo ejemplo, vamos a tener que instalar una estrategia. Las estrategias
(como Passport las llama) no son otra cosa que mtodos que tenemos para autenticarnos en la
aplicacin. Una estrategia puede ser un usuario local y otra puede ser Twitter, Facebook o Google.
Para nuestro ejemplo, vamos a usar la autenticacin local, esto es, sin contar con servicios de terceros.
Para ello, necesitamos instalar dos cosillas:
connect-flash : Este mdulo nos permite pasar mensajes de error con el inicio de la sesin.
passport-local : Este mdulo es, en s, la estrategia local que nos vamos a encargar de
configurar.
Adelos 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/
Gestin de Login con Passport 121
1 $ npm install
Ahora que tenemos todo instalado y listo para usar en nuestra aplicacin, 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 estn 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 aplicacin
Vamos primero a nuestro archivo estrategia-local ya que en la aplicacin 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){
Gestin 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 contrasea 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
Gestin de Login con Passport 123
51 }
52 };
Lo primero que hacemos es traernos el objeto Strategy del mdulo passport-local que instalamos.
La estructura que vemos ahora es la tpica para exportar varias funciones. Como ves, necesitamos
los usuarios, que pasaremos como parmetros.
La funcin encontrarUsuario se encarga de recorrer la lista de usuarios y comparar el campo
usuario de cada objeto y, si lo encuentra, ejecuta la funcin de callback que recibe como segundo
parmetro. En caso de no encontrarlo, pasamos el valor null para que sepamos que no hemos llegado
a encontrarlo.
Si has estado prestando atencin, sabrs que aseguraLogin es un Middleware. Este Middleware lo
usaremos para proteger las rutas que queramos que estn protegidas por el inicio de sesin. Passport
nos facilita el mtodo isAuthenticated que nos devuelve true si el usuario tiene sesin iniciada y
false en caso contrario. Si ha iniciado sesin, llamamos a next para que la ruta contine su curso.
En caso contrario, guardamos la ruta a la que quera ir al usuario en la sesin en el valor rutaLogin
y lo redirigimos a /login para que inicie sesin.
Sobre esta tcnica
Cuando un usuario visita una pgina que est protegida por contrasea, lo que espera es
que al logarse, pueda volver automticamente a esta pgina. Esto es especialmente til
cuando dejas de usar la aplicacin por un tiempo que provoque que la sesin caduque
y, al volver, se pierda. No obstante, es una caracterstica fcilmente eliminable. Intenta
mejorar la experiencia de tus usuarios!
La funcin serializarUsuario es una funcin de Passport que es necesaria en caso de valernos
de sesiones (como ser nuestro caso). Bsicamente es la funcin que usa Passport para serializar al
usuario cuando el inicio de sesin tiene xito. En nuestro caso, nos valemos del campo usuario para
que los datos guardados en sesin sean los mnimos posible.
Por el contrario, la funcin deserializarUsuario se encarga del proceso contrario. A raz del
usuario, nos devuelve el objeto completo del usuario gracias al uso de encontrarUsuario. A raz de
esta funcin, Passport es capaz de dejarnos en el objeto req.user, todos los datos del objeto usuario,
es decir su nombre, contrasea y usuario en nuestro caso.
Lo ltimo que definimos es la Estrategia. La estrategia recibe el usuario y la contrasea y una funcin
de callback. Gracias a la funcin encontrarUsuario, lo buscamos por el campoUsuario. Si hay un
error, se lo pasamos al callback. Si por desgracia, la contrasea no coincide, pasamos al callback
tres parmetros: El primero como siempre indica el error, en este caso al no ser un error de Node,
pasamos null. El segundo indica que la autenticacin no ha tenido lugar. El tercero (y opcional),
Gestin de Login con Passport 124
es un objeto con el campo message que se pasar como error de inicio de sesin. Si la contrasea
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. Adems, 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 sesin 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 estars pensando, y
no comprobamos el usuario ni nada? No es que sea Mentalista ni nada, como dira Patrick Jane, los
mentalistas no existen. No obstante, es lo que yo pensara si fueras t De toda la parte desagradable
se encarga la estrategia que definimos antes, te acuerdas? Ahora veremos cmo encaja todo este
puzzle.
Gestin 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 contrasea ya que vamos a mostrar todas las contraseas 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');
Gestin 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 tambin 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. Adems, le indicamos las funciones serializeUser y deserializeUser.
En nuestra pila de Middlewares de Express hemos colado 3:
Gestin 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 (tambin opcional) permite a Passport el uso de sesiones persistentes. Es muy importante
que passport.session est despus de exporess.session para que todo funcione correctamente.
En nuestras rutas, vers que /usuarios pasa primero nuestra estrategiaLocal.aseguraLogin que
se asegura de que hayamos iniciado sesin en el sistema antes de enviar informacin sensible.
En la ruta post de /login, como primera funcin pasamos passport.authenticate('local'). Esto
lo que hace es uso de nuestra estrategia local para iniciar sesin. Pasamos un par de parmetros en
un objeto de configuracin: failureRedirect es a dnde ser redirigido el usuario en caso de que
la autenticacin no tenga xito. failureFlash se encarga de que haya mensajes flash en la sesin.
Si la autenticacin 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") Contrasea
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 prrafo con el error
de inicio de sesin.
Es importante que el usuario tenga como name el valor username y la contrasea password pues
esto es lo que espera Passport y, si no, nos devolver un error por defecto: Missing credentials
En caso de que quisiramos cambiar este comportamiento, tenemos que hacerlo en la estrategia:
Gestin de Login con Passport 128
1 new EstrategiaLocal({
2 usernameField: 'usuario',
3 passwordField: 'contrasea'
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 ms real posible (dado que lo habitual es usar
una base de datos), sirve como base para ilustrar un proceso de login habitual de una aplicacin en
Node.js con Express y Jade. Es fcil sentirse abrumado al principio pero, una vez implementado,
proteger una ruta es un proceso realmente sencillo que nicamente necesita aadir 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 travs de la aplicacin. 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 ningn mdulo extra
ni nada por el estilo, Express cuando lo sacamos de su caja puede hacerse cargo de la situacin l
solo. Es todo un nio mayor.
Vamos a empezar por la parte fcil. Lo primero que necesitamos es un formulario que nos permita
poner ficheros. Debera 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 codificacin. La primera, y es la que se usa por defecto
es application/x-www-form-urlencoded. Esta no tenemos porqu especificarla nunca. Podramos
por supuesto pero hara los formularios ms horribles de lo que ya son. Bsicamente, esta codifica-
cin es similar a la que usamos cuando tenemos algo as: mi_pagina/?parametro1=valor1&parametro2=valor2.
Imagina lo que pasara si pones un archivo en ese tipo de formato. Directamente petara. El servidor
no sabra cmo volver a unir los datos y habramos 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 mtodo POST para estos casos. Pero tampoco es una pega real,
no?
Ahora que ya tenemos el formulario, tendremos que aadir algo para poder adjuntar el fichero, el
tpico botn que dice Escoger archivo. No debera 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 aadir un botn para enviar el formulario. El campo input de
tipo file no tiene ningn 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 envo (que estoy seguro de que te ha
resultado muy complicado), vamos a prepararnos para la recepcin. Es igual de complicado. Ya lo
vers.
Recuerdas este Middleware?
1 app.use(express.bodyParser());
Esta es la definicin que le d:
Este Middleware es el encargado de procesar el cuerpo de las peticiones que llegan a
nuestra aplicacin. Para que nos entendamos, es el encargado de que una peticin POST
coloque los parmetros en req.params o nos facilite los archivos de haberlos.
Pues, como podrs imaginar, es el que se encarga de que podamos hacernos con el fichero. Sin l,
nada sera 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 envo del formulario. Lo hemos definido como method pero es que adems,
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 });
Segn Express, los archivos que se han subido quedan visibles en el parmetro files de la peticin.
Si intentamos hacer una subida, deberamos 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.
Adems tenemos una serie de datos interesantes:
size : Este es el tamao en bytes del archivo. til si queremos realizar algn tipo de validacin
de tamao.
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 cmo podemos
cambiar eso
name : Es el nombre original que el usuario le dio al archivo, junto a su extensin.
type : Es el tipo de archivo que hemos subido. Es otro dato interesante si queremos hacer
validaciones. Imagina que estamos mostrando una galera de fotos. Solo querremos que la
gente suba imgenes, no? Pues este es el parmetro que nos dice qu es el archivo en s.
El resto de parmetros y funciones no me parecen del todo interesantes ni necesarios en la mayora
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 extensin 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 ubicacin temporal. Esa es la de Windows. En sistemas Unix el directorio
temporal, como no podra 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 aplicacin que se llame temp,
idealmente en la raz de la misma. De esta forma, no queda visible directamente el contenido ni
es servido bajo ningn concepto. Una vez que hayamos creado la carpeta, tenemos que informarle
a Express de que queremos que ese sea el nuevo directorio de subidas. Dnde? Pues en nuestro
amigo, el Middleware:
1 app.use(express.bodyParser({ uploadDir:__dirname + '/temp/' }));
Pasando un objeto de configuracin a bodyParser podemos modificar levemente su comportamien-
to. Otra opcin que tiene es keepExtensions. Si establecemos esta propiedad a true, el Middleware
se encargar de que la extensin 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 ms descriptivo, dnde va a parar! [/irona]
Ahora que ya tenemos el archivo ubicado, y est encima en nuestro sistema, podemos trabajar con
l fcilmente. 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 funcin para ello,
estoy seguro de que podrs hacer un bonito Middleware que se encargue de validarlo, verdad?
!VERDAD?!
Bueno, en vez de un Middleware, voy a poner el nombre de una funcin. Ahora vers 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. Adems, usamos el nombre original del archivo. Ahora estars 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 vlido, confiando en el usuario,
usamos la funcin rename. El nombre es un poco engaoso ya que puedes pensar que lo que hace es
renombrar el fichero. Para eso podran 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 querra ver un archivo que hace unos segundos
tena en su escritorio pero, algo tena que poner ah!
En caso de que el archivo no pase la validacin nosotros somos los encargados de borrar el fichero
de su ubicacin temporal. Para ello usaremos la funcin unlink que, si alguna vez has usado PHP,
te sonar ya que tiene exactamente el mismo nombre. Lo s, en estos momentos estars pensando,
pero si PHP y Node.js son como Manzanas y Coliflores No se parecen! Pues tienen algo en comn:
el lenguaje C. La funcin unlink es una funcin de C y, como comentamos al inicio del libro, una
parte nada desdeable de Node.js es C y PHP, a bajo nivel, es C tambin. Como dice el dicho, nunca
te acostars sin saber una cosa ms!
Como te habrs fijado, tanto el mover el fichero como el borrarlo, son operaciones asncronas por
lo que el servidor puede seguir trabajando mientras espera al resultado de la misma. Puro estilo
Node.js!
A que ha sido fcil? S? Pues vamos a echar un brevsimo vistazo a cmo cambian las cosas si
queremos subir varios archivos a la vez.
Subiendo varios ficheros
Volvamos a nuestro formulario. Estoy seguro de que ests 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 aadir la propiedad multiple al campo para que acepte mltiples
ficheros. Si recargas el formulario, y abres la pgina, vers 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 imaginrtelo. 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 funcin 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 operacin que
hicimos antes pero esta vez para mover cada fichero. Adems, dado que no tiene sentido redirigir al
usuario a cada foto Le decimos que ya ha terminado todo!
nimo, prubalo en tu servidor y vers cmo los ficheros aparecen en la carpeta de ficheros. No
dirs que te ha costado trabajo ehh! Ahora ya podrs decir que sabes poner un formulario de subida
de ficheros para que la gente suba cosas en tu aplicacin.
Resumen
En este captulo final hemos visto un montn 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 ms en Apndices.
Pero no nos desviemos.
En este captulo nos hemos metido de lleno con Express. Hemos visto cmo podemos instalarlo
fcilmente y cmo aprovecharnos de la instalacin global para que nos genere automticamente
una estructura de carpeta por nosotros con tan solo un sencillo comando. Adems hemos explorado
la estructura de carpetas que nos crea, aprovechando para ver en perspectiva la estructura de carpetas
que vimos en el captulo anterior.
A continuacin, hemos visto cmo podemos configurar Express en profundidad repesando cada una
de las opciones de configuracin habidas y por haber. Incluso las que no estn en la documentacin!
Gracias a la configuracin podemos cambiar el comportamiento del framework y, lo que es mejor,
podemos establecer nuestros propios valores de configuracin.
Luego, nos hemos sumergido en las rutas con profundidad ya que, en el fondo, son el corazn
de Express. Hemos aprendido cmo usar parmetros, cmo hacer que estos sean opcionales e
incluso expresiones regulares. Finalmente tambin hemos podido ver las cadenas de bsqueda como
parmetros y cmo poder extraer los datos de la cadena y, adems, hemos descubierto cmo podemos
agrupar los parmetros para formar matrices de valores con una misma clave.
Uno de los temas quiz ms complejos, ha sido el de los Middlewares. Esas pequeas (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, adems, 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 (peticin) nos pueden dar. Por ejemplo ahora sabemos cmo obtener la IP de la peticin
o si una peticin es AJAX o no. La respuesta quiz haya sido ms interesante porque, finalmente,
es lo que devolvemos al usuario as que hemos visto cmo redirigir a otras rutas, aprendido algunas
cosas sobre los cdigos 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
aplicacin dentro de las plantillas y adems hemos visto cmo evitar que las plantillas exploten por
no tener una variable presente. Hemos descubierto lo tiles que son los bloques de cdigo en Jade los
cuales podemos usar para iterar sobre objetos y/o matrices o modificar dramticamente la apariencia
en funcin del valor de una variable.
En la recta final del Captulo, hemos aprendido algunos trucos para mejorar la experiencia o aadir
alguna funcionalidad a nuestra aplicacin. Primero, hemos visto cmo podemos trasladar al usuario a
Resumen 137
pginas personalizadas cuando obtienen un error 404 o 500 gracias al uso de los Middlewares. Luego,
hemos aprendido a aadir un sistema de Login bsico con Passport a Express creando una pequea
aplicacin con usuarios (locales) de prueba. Finalmente, hemos tratado cmo realizar subidas de
fichero a nuestro servidor desde un formulario tanto de un solo fichero como de mltiples a la vez
e, incluso, hemos aprendido cmo podemos hacer una validacin bsica 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 intencin es la de aadir un par de Apndices con dos aplicaciones (no demasiado
complejas) para tratar base de datos (probablemente con Mongo) y Socket.io. Ambos apndices,
quiz sean los ms largos por la cantidad de cdigo que contendrn, aunque ya tenemos la mayora
de los conocimientos para hacer esas aplicaciones!
Como deca Desmond:
Te ver en otra vida, colega
Apndice A: Ejecuta tu aplicacin
Node.js siempre
Perfecto. Ya tienes tu aplicacin de Node.js. Se la has enseado a tu jefe y est contento. Ha llegado
la hora de dar el salto, de ponerlo en produccin y disfrutar de tu obra maestra. Llegas al servidor,
lo subes y escribes:
1 $ node app.js
Todo el mundo puede acceder. Corre el champn y te sientes en la cumbre. Maana pedirs 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 ests
en las nubes. De repente alguien no te deja salir. La aplicacin ha dejado de funcionar.
Quiz no a un nivel tan pico, (o quiz solo lo fue para mi) pero yo estuve en esa situacin.
Obviamente antes de pasarlo a produccin. O eso creo. El caso es que en cuanto cierras la ventana
del terminal, todo parece terminar. Lo ms habitual es que queramos que esa aplicacin se ejecute
por siempre jams as que empiezas a maldecir al creador de Node.js.
No te preocupes! Vamos a ver cmo podemos solucionar esto.
Atencin
Lamentablemente este captulo 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 apndice.
Ejecutando Node.js en segundo plano
Si eres un Linuxero bueno, seguro que se te ha ocurrido que podras ejecutar el servidor con nohup.
Literalmente, nohup significa que el programa ha de ignorar la seal HUP (cuelgue) que es bsicamente
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
aplicacin. Puedes repasar este tema en el Captulo 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 aplicacin y puedes empezar a sonrer. Pero esta vez la alegra dura menos. Un usuario entra
en una ruta extraa y provoca un error que no tenas 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 tendrs tiempo de arreglarlo.
Usando forever
Forever significa literalmente en ingls siempre. As que supongo que es un mdulo que nos viene
que ni pintados para este caso. Verdad? Siempre significa siempre, ms le vale que sea verdad.
Forever es una aplicacin 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 ms de
un proceso ejecutndose a la vez y porque, sencillamente, forever es un programa de la lnea de
comandos.
1 $ npm install -g forever
Forever se ocupa de que, si la aplicacin cae, se vuelva a levantar. El comando para levantar la
aplicacin sera:
1 $ NODE_ENV=production forever start app.js
Y ahora, s que nos podramos ir a casa porque la aplicacin quedar levantada para siempre. Pero
esto no acaba aqu. Sigue leyendo antes de irte porque puede que lo necesites.
Forever habr puesto nuestra aplicacin a funcionar y nicamente dir algo como:
1 info: Forever processing file: app.js
Si ahora abrimos nuestro navegador e intentamos acceder, vers que, efectivamente, aparecer todo
perfecto. Si ejecutamos la ruta que hace que la aplicacin falle, vers como se vuelve a levantar sola.
Descubre ms
Forever est ubicado, como no, en un repositorio de Github en el que, adems, podemos
encontrar toda la documentacin 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 ms 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, querrs saber qu procesos son los que se estn ejecutando. Puede que tengas uno
solamente pero siempre es bueno saber si se est o no ejecutando ese proceso que, creemos, debera
estar levantado.
Para ello no tenemos ms que ejecutar lo siguiente en el terminal:
1 $ forever list
Y forever, obedientemente, nos devolver una lista de los procesos que hay ejecutndose:
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 ms importante que sacamos de ah es esta informacin:
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 aplicacin ejecutndose sin caerse: 0:0:0:4.252
Si esperamos un rato y volvemos a ejecutarlo, vers como el tiempo (crucemos los dedos) aumenta.
Paradlo todo!
Seguro que hay veces en las que quieres parar la ejecucin 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 ejecucin de la aplicacin y forever te dir los procesos que ha detenido. Por si no
fuera suficiente, tenemos tambin la opcin de pararlo todo. Para ello usaremos la opcin stopall
1 $ forever stopall
Y, nuevamente, recibiremos una lista de todos los procesos que ha detenido. Fcil, no?
Reiniciando la aplicacin
Ya sabes cul es la regla de los informticos. Si algo no funciona, has probado a reiniciar? Pues,
afortunadamente puedes reiniciar la aplicacin, como si ocurriera un fallo pero controlado. Lo ms
habitual es que hagas esto despus de hacer una actualizacin, ya que necesitas reiniciar el proceso
si actualizas el cdigo de la misma.
Nuevamente, tenemos dos opciones:
1 $ forever restart app.js
Y, nuestra aplicacin, 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 ejecucin
Ubicacin de los logs
La primera de la que voy a hablar es la de ver el log. Podramos 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 dnde 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 mtodo que escojas, puedes usar tail -f para ver lo que pasa con tu aplicacin,
especialmente en el log de errores.
Consejo extra
Lo ms habitual es que si estamos usando forever para mantener nuestra aplicacin con vida,
queramos que si el equipo se reinicia, la aplicacin vuelva a arrancar cuando el sistema, por el
motivo que sea se reinicie tambin.
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, tendrs que valerte de sudo.
Una vez que ests en el editor, aade la siguiente lnea:
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, deberas 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 automtica-
mente. Genial! Ahora puedes volver a hablar con tu jefe y pedirle ese aumento de sueldo.
Apndice B: Creando un chat con
Socket.io
Ahora que ya tenemos algo ms de conocimientos, podemos pasar a crear una aplicacin ms
compleja, que ponga a prueba todas nuestras habilidades y, de paso, porqu no, aprender un poco
ms.
En esta aplicacin 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 ms de protagonismo.
Qu es Socket.io?
Socket.io es, de primeras, un mdulo de Node.js. S, de esos que podemos instalar con un npm
install. El mdulo est pensado para facilitar la comunicacin en tiempo real. Hoy en da, los
navegadores modernos tienen Websockets que permiten una comunicacin constante entre cliente
y servidor. De esta manera, tanto uno como el otro, pueden estar hablando contnuamente y
manteniendo al usuario de la aplicacin al da de lo que va ocurriendo. No obstante, hay navegadores
que no soportan Websockets de forma nativa y, para ello, surgi Socket.io.
El mdulo se compone de dos partes: Una librera del lado del cliente que se ejecuta en el propio
navegador del usuario, y una librera en el lado del servidor. Vamos a intentar explicar un poco cmo
funciona esto.
Cuando un usuario se conecta al servidor, se produce lo que se conoce como handshaking que,
literalmente significa apretn de manos. Lo que ocurre es que tanto cliente como servidor, identifican
cul es la mejor forma de comunicarse entre s. Si tenemos a mano los websockets, todo ser mucho
ms rpido pero, si no, Socket.io intenta usar una de estas tecnologas:
flash socket : Es un socket basado en Adobe Flash y es el que utilizan las aplicaciones Flash
para mantener una conexin abierta con el servidor.
jsonp polling y ajax long-polling : Esta tecnologa se basa en el envo de una peticin desde
el cliente al servidor. El servidor no responde inmediatamente si no que espera hasta que haya
nueva informacin disponible. Cuando tenemos nueva informacin, el servidor la enva. Si se
excede el tiempo de espera o se recibe una respuesta, el cliente volver a hacer otra peticin
al servidor volviendo a iniciar el ciclo.
145
ajax polling : En vez de mantener la peticin abierta, el cliente hace una peticin cada 5
segundos al servidor (normalmente).
El recurso de estas tecnologas es para navegadores antiguos, es decir, Internet Explorer desde antes
de la versin 10.
Ahora que ya sabemos lo que es, vamos a meternos un poco en faena.
La aplicacin que vamos a crear
Dado que Socket.io es una tecnologa 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 ms sencillo de explicar y al que ms utilidad se
le ve rpidamente, sin necesidad de tener otros sistemas adicionales.
Hasta ahora he usado Socket.io en dos proyectos ms:
Gestin de centralita Asterisk en tiempo real con duraciones de llamadas, agentes que logan
y deslogan en sus telfonos, etc.
Visualizador de logs multiservidor en el navegador.
No obstante, he aqu una pequea captura que muestra la aplicacin en accin:
Nuestra aplicacin 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 cdigo en la pgina.
He colgado adems el cdigo en Github, por lo que podis 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 deberas 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 opcin:
1 $ npm install socket.io --save
Bien! Ahora ya tenemos todas las herramientas necesarias para poder crear nuestra aplicacin.
Primero vamos a ver el cdigo de Node de la aplicacin. 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 podrs comprobar, estamos aprovechando la estructura por defecto de Express a la hora de
crear una aplicacin.
En las dependencias, hemos includo 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 lgica 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 continuacin, lo que hacemos es aadir la parte de socket.io. Creamos una variable de nombre
io (por convencin) y le decimos que escuche al servidor de Express que hemos creado ahora mismo.
En solo dos lneas de cdigo!
Luego creamos dos variables, usuarios y mensajes que contendran la lista de usuarios y mensajes
que vayan fluyendo por el chat. De momento vacas.
Antes de configurar Express, nos disponemos a configurar Socket.io. Estas son las opciones que
pasamos:
Minificacin, compresin y cach de la librera en el cliente (esas son las 3 primeras). Durante
el desarrollo las puedes eliminar si quieres bichear en el cdigo en caso de que obtengas fallos.
149
El nivel de log al mnimo. Si no ponemos eso, aparecern multitud de mensajes de los sockets
que se van conectando.
Por ltimo, especificamos, en orden de preferencia, los Sockets que queremos. Estn ordenados
de ms rpidos a ms lentos.
La parte intermedia del resto del archivo, es cdigo normal que debera sonaros a estas alturas. Si
no es as volved al principio!
Ahora vamos a la parte que ms nos interesa. Socket.io nos expone un objeto sockets que no es otra
cosa que un EventEmitter.
Recuerda
Hablamos sobre EventEmitter en un captulo anterior y quiz sea buena idea que
refresques los conocimientos. No dejes de releer el captulo!
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 tambin.
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 ms de uno con el mismo nombre.
mensaje : Este es el evento que ocurre cuando un usuario enva un mensaje al chat. La funcin
solo se ejecuta cuando obtenemos el valor del usuario del Socket, que es una operacin
asncrona. En seguida veremos ms sobre esto.
disconnect : Este es el evento que ocurre cuando un usuario abandona nuestra aplicacin.
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 da en una empresa de seguridad.
Tu jefe, el primer da, te dir algo as:
Si alguien entra en esta habitacin sin autorizacin, pides refuerzos y lo arrestas.
Si alguien atraca a alguien, acude cuanto antes para intentar arrestarlo.
Cada da 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 ms entrar les tenemos que decir lo que
tienen que hacer en funcin 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.
Conexin
En la primera, cuando un usuario se conecta, ejecutamos una funcin que recibe los siguientes
parmetros:
socket del usuario que se acaba de conectar.
usuarios que ya estn 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 funcin 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 nmeros 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 funcin 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 operacin asncrona, nos da igual cundo
termine.
En una matriz temporal, guardamos el usuario que acaba de conectarse. Luego entenderemos porqu.
Ahora es cuando ocurre la magia. Vamos lnea a lnea que creo que merece la pena:
152
1 socket.broadcast.emit('conexion',temp);
Esta lnea 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 lnea 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 lneas 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
pequea lista que puedes imprimir en tamao 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 funcin del mensaje recibe como parmetro el socket que ha enviado el mensaje, la lista de
mensajes, el usuario que ha enviado el mensaje (obtenido del socket con la funcinsocket.get('usuario'))
y los datos que no es otra cosa que un objeto que contiene un parmetro mensaje que tiene el mensaje
que acaba de ser enviado.
La funcin se encarga de enviar el mensaje a todo el mundo, aadiendo el tipo de mensaje y el
usuario que lo ha enviado y lo guarda en la lista de mensajes.
153
Desconexin
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 desconexin es nuestra ltima funcin y recibe el socket traidor que est abandonando nuestra
aplicacin, la lista de usuarios actual, y el usuario traidor que ha decidido abandonar.
Esta funcin 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 cmo
engancha todo. No te pierdas!
Antes de empezar con el cdigo JavaScript, vamos a ver la plantilla de Jade, que como vers, 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 botn de enviar, etc.
Lo nico que quiz te sorprenda es el archivo de socket.io. Si miras tus carpetas, vers que no tienes
ninguna carpeta que se llame as. No obstante, Express y Socket.io son viejos amigos, y gracias a la
154
configuracin que hemos hecho antes, Express ser capaz de servir el archivo de Socket.io necesario
para haya comunicacin con el servidor.
Ahora s, ests preparado? De verdad? Pues sigue leyendo, vamos a ver nuestro archivo que
contiene toda la lgica 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 ms cargar la pgina, pedir el usuario con la funcin prompt
de JavaScript. Por defecto, el valor ser Usuario pero el usuario (valga la redundancia) es libre de
cambiarlo.
Saltmonos 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. Fcil, no?
Ms informacin
Si te has fijado, todo el cdigo est envuelto en una funcin annima auto-invocada de
JavaScript. Este es un patrn muy conocido pero, si por algn motivo, te resulta ajeno, te
recomiendo que te leas un artculo 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 mtodos.
Dentro de el, guardamos todos los elementos del DOM que hemos creado en nuestra plantilla. Esto
est considerado una buena prctica ya que solo buscamos en las tripas del DOM una vez al
principio!
La funcin 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. Bsicamente es esta sentencia:
1 this.socket = io.connect('http://localhost:3000');
En este momento es en el que, en el servidor, se ejecuta la funcin que le pasamos al mtodo
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 funcin 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 enva un mensaje.
click : Cuando alguien hace click en el botn de enviar, significa que nuestra aplicacin tiene
que enviar el mensaje que hay en textoMensaje al servidor y de ah, se propagar al resto.
Todas estas funciones usan la funcin de jQuery $.proxy que no es otra cosa que una funcin que
toma una como parmetro, y devuelve otra que se ejecutar en el contexto especificado.
Eso queda muy bonito pero, qu quiere decir?
Bueno, si habis jugado con JavaScript, sabris que el elemento this tiende a perderse cada poco.
Por ejemplo, en la funcin del botn de click, si no usramos $.proxy, el valor de this sera el del
botn. Eso resulta realmente molesto as que de esta forma lo solucionamos de manera que this
siempre ser nuestro objeto Chat.
Ms informacin
Una vez ms, este es uno de los temas peliagudos de JavaScript que necesitan un
momento de comprensin. Hace tiempo escrib un artculo 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 aadamos un nico usuario
a una matriz temporalmente ya que de esta forma, podemos usar esta funcin para aadir tantos
usuarios como queramos. Gracias a la funcin each de jQuery, vamos iterando por la matriz para
crear nuestra cadena HTML que aadiremos a la lista de usuarios.
La funcin 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 prrafo
y le aadimos una clase en funcin del tipo. Si viene el usuario, pues lo aadimos al principio del
mensaje para que sepamos quin 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 funcin enviarMensaje es la que se encarga de gestionar el click sobre el botn enviar, lo primero
que hacemos es prevenir el comportamiento por defecto del botn 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 parmetro
mensaje despus de haberlo escapado.
Y como ves, eso es todo. Gracias a la separacin de conceptos, minimizamos enormemente la canti-
dad de funciones y cdigo que tenemos que escribir, tanto anadirMensaje como anadirUsuariosAChat
son funciones realmente genricas y que se usan tanto en la conexin inicial para aadir todos los
mensajes y usuarios, como cuando ocurre de forma individual.
Ahora te toca a ti, destripar la aplicacin, instalarla e intentar mejorarla!