Descubriendo Nodejs Express

October 1, 2017 | Author: Jaime Mugica | Category: Java Script, Web Server, Hypertext Transfer Protocol, Technology, Computing
Share Embed Donate


Short Description

Download Descubriendo Nodejs Express...

Description

Descubriendo Node.js y Express Aprende a desarrollar en Node.js y descubre cómo aprovechar Express Antonio Laguna Este libro está a la venta en http://leanpub.com/descubriendo-nodejs-express Esta versión se publicó en 2013-07-02

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

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

También por Antonio Laguna Laravel: Code Happy (ES)

Índice general Introducción . . . . . . Dedicado a… . . . . . Errare humanum est Feedback . . . . . . . Libro en desarrollo .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

i i i i ii

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

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

iii v v vi

Introducción a Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

Node.js basado en eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

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

3

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

. . . . . .

5 5 6 7 7 8

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

9 10

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

. . . .

15 15 17 17

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

19

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

21

. . . .

. . . .

. . . .

. . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

. . . . . .

. . . .

ÍNDICE GENERAL

NPM - Node Packaged Modules . . Búsqueda de paquetes . . . . . . . Obtener información de paquetes Lista de paquetes instalados . . . Instalación de paquetes . . . . . . Desinstalación de paquetes . . . . Paquetes útiles y habituales . . . . Dudas frecuentes . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

23 23 24 24 25 27 27 27

Cómo mantener Node.js actualizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sobre las versiones de Node.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

29 30

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

31

Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

34

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

36 37 38

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

. . . . .

42 42 43 45 47

Organizando el código de nuestra aplicación . . . . . . . . . . . . . . . . . . . . . . . . . El archivo de configuración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

49 50

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

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

53 53 53 54 55 55 56 58

Los Streams en Node.js ¿Que es un Stream? . La función pipe . . . Lectura - Readable .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

60 60 61 61

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . . .

. . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

ÍNDICE GENERAL

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

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

63 66 66 67

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

68 68 69 70

Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

72

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

. . . .

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

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

74 74 74 75 75 76

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

77

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

78

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

80

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

83 83 84

Rutas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Cadena de búsqueda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

87 90 93

Middlewares . . . . . . . . . . . . . . app.use . . . . . . . . . . . . . . . . En línea . . . . . . . . . . . . . . . Mapeado . . . . . . . . . . . . . . . En resumen . . . . . . . . . . . . . Middlewares ofrecidos por Express .

95 95 97 98 98 99

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

La petición - request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 req.body . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 req.param(parametro) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

ÍNDICE GENERAL

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

req.xhr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 La respuesta - response res.status . . . . . res.redirect . . . . res.send . . . . . . . res.jsonp . . . . . . res.sendfile . . . . res.download . . . . res.render . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

104 104 104 105 105 106 106 107

Plantillas con Jade . . . . . . . Sintaxis básica . . . . . . . . Anidando elementos . . . . Variables en Jade . . . . . . Bloques de código auxiliares

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

108 109 112 112 115

Páginas de error en Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Gestión de Login con Passport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Gestionando la subida de ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Subiendo varios ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

Apéndice A: Ejecuta tu aplicación Node.js “siempre” . . . . . . . . . . . . . . . 138 Ejecutando Node.js en segundo plano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 Usando forever . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 Operaciones con los procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 Consejo extra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

Apéndice B: Creando un chat con Socket.io . . . . . . . . . . . . . . . . . . . . . 144 ¿Qué es Socket.io? . . . . . . . . La aplicación que vamos a crear Instalando las dependencias . . El lado servidor . . . . . . . . . El lado del cliente . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

144 145 146 146 153

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

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

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

Introducción

ii

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

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

Introducción

iv

Según la página de Node.js, esta es la definición: Node.js es una plataforma creada sobre el motor JavaScript de Chrome para crear aplicaciones de red rápidas y escalables. Node.js utiliza un modelo basado en eventos y entrada/salida que no bloquea, lo cual lo hace ligero y eficiente, perfecto para aplicaciones que usen muchos datos en tiempo real que puedan ejecutarse de forma distribuida en varios dispositivos. Prometedor… ¿no? Además, puede que te interese saber que compañías como Microsoft, Linkedin o Yahoo ya están usando Node.js para el software. ¿Un servidor? Sí, has leído bien. Node.js es capaz de crear un servidor web y no tendrás la necesidad de usar algo como Apache⁵, lighthttpd⁶ o Nginx⁷. Pufff… ¡qué complicado! ¡No! ¡No lo es! Y eso es lo que pretendo demostrarte en este libro. Todas las personas que conozco que han comenzado con Node.js, han acabado disfrutando mucho del entorno porque tienes muchísima libertad a la hora de escribir código y sientes que todo está en tus manos. Para que te hagas una idea, crear un servidor ocupa tan solo seis líneas de código… ¡como mucho! 1

var http = require('http');

2 3 4 5 6

var server = http.createServer(function (request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.end("¡Hola Mundo!\n"); }).listen(8080);

7 8

console.log('Servidor escuchando por el puerto 8080');

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

Introducción

v

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

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

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

Introducción

vi

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

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

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

Créeme cuando te digo que no tienes por qué preocuparte, ya que no vamos a usar ningún comando raro en ningún caso. ¿Confiarás en mi? El pilar fundamental de Node.js, es que está basado en eventos y su naturaleza de no bloqueo. No, no estamos hablando del tipo de bloqueo del parchís. Si no de operaciones que bloquean (o no) el servidor. Vamos a meternos un poco más en faena.

Node.js basado en eventos La aplicación habitual de Node.js, se queda residiendo en memoria y espera algo por parte del sistema para ejecutar una función. Esto puede ser alguien entrando en una página web, un cambio en un fichero de un directorio o un mensaje que llegue por un socket. Piensa en ello como una gasolinera 24 horas. Está ahí abierta y puede pasar horas sin que nadie vaya, pero eventualmente alguien pasará a repostar y estará lista para surtir gasolina. ¿Y qué hace durante el resto del tiempo? Nada. De hecho es bueno que no haga nada ya que es necesario para que se active el recolector de basura de JavaScript. ¿Basura? ¿Qué pasa si no se recolecta? Los recolectores de basura existen en la gran mayoría de los lenguajes. Son como eso que imaginas solo que no son personas. Es un proceso que se encarga de revisar el código que está en ejecución y decir… 1 2 3 4

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

Estoy seguro, lector, de que ya sabes lo que pasa si no recolectamos la basura. Que, eventualmente, nos quedaríamos sin memoria y eso sería una catástrofe. Todo empezaría a ir lento. ¡No solo la aplicación! ¡Si no el sistema! ¿Y puedo yo llamar al recolector de basura? No, no puedes ni debes. Si seguimos buenas prácticas de programación no tendríamos nunca que preocuparnos por este proceso invisible. Vale Antonio, ¿pero todo esto no lo puedo hacer con Python o PHP? Seguro que estás pensando en eso. Mucha gente lo piensa, especialmente los Javasaurios. Y tienes razón. Node.js no es la panacea y no hará todo el trabajo por ti. Es una herramienta que, en manos inexpertas puede parecer poca cosa. En este libro vamos a intentar aprovechar todo ese potencial que está dando tan buenos resultados por ahí. Node.js es especialmente bueno cuando queremos hacer cosas a la vez. ¿No te has visto en la tesitura nunca de decir, ojalá pudiera hacer cosas en paralelo? Pues en Node.js eso es lo que ocurre. Bueno, más o menos. Vas a tener que confiar en mi, un pequeño salto de fé. Si lo hiciste con Altair, ¡seguro que puedes lograrlo!

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

Ejemplo 1: Código tradicional síncrono 1 2 3 4

var resultado = db.query("SELECT * FROM usuarios"); // Esperamos a los resultados hacerAlgoConEllos(resultado); // Esperamos a que termine hacemosOtraCosa();

Ejemplo 2: Código asíncrono 1 2 3 4 5 6

db.query("SELECT * FROM usuarios", function(resultados){ hacerAlgoConEllos(resultado); // Esperamos a que termine }); // Esta función se ejecuta justo después de que se haya lanzado la consulta // antes de obtener los resultados hacemosOtraCosa();

Y… ¿realmente se ejecutan las cosas a la vez? La respuesta es no.

La asincronía por naturaleza

4

Aquí está el engaño… Te dije que confiaras en mi. En apariencia se ejecutan a la vez. Sin embargo, tras el telón, lo que realmente pasa es que las callbacks quedan en una pila y se van ejecutando. Hasta que ese callback termine, el resto tendrá que esperar. Lo que ocurre, es que el motor de Node.js es tan rápido y normalmente no usamos procesos tan pesados que, en apariencia, se ejecutan a la vez. La parte negativa, es que no sabemos tampoco en qué orden se ejecutan. Para los que les gustan las explicaciones técnicas, lo que realmente ocurre es que JavaScript, tanto el cliente como en el servidor, se ejecuta en un único hilo, por lo que es técnicamente imposible que haga dos cosas a la vez. Pero confía en mi, esto no es ningún problema. Esto puede resultar un poco confuso, especialmente al principio. Pero si me vas siguiendo no deberías perderte en el camino de la asincronía.

Instalando Node.js Para no aturdirte en demasía, vamos con algo ahora sencillito. Así vamos intercalando una de cal y otra de arena. Vamos a ponernos manos a la obra y a instalar Node.js para poder empezar a cacharrear que es lo que a todos nos gusta, ¿verdad? Como verás es un proceso realmente sencillo.

Instalando en Windows y Mac El proceso de instalación en ambos sistemas es exactamente el mismo. 1. Nos dirijimos a la página de Node.js: http://nodejs.org/ 2. Le damos al botón grande y verde que pone INSTALL. Esperamos. 3. Una vez finalice la descarga, tendremos un archivo .msi o .pkg depende del sistema en el que estemos. Lo localizamos y hacemos doble click. 4. Finalmente, seguimos el proceso de instalación que es bastante sencillo. 5. Profit.

6

Instalando Node.js

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

Instalando en Linux Cubrir cada distribución de Linux es una locura, pero el proceso no debería variar mucho. En este caso, vamos a ver cómo instalar Node.js en Ubuntu. 1. Nos dirijimos a la página de Node.js: http://nodejs.org/ 2. Le damos al botón de DOWNLOAD. 3. En la siguiente página, tendremos que elegir el código fuente de Node.js que, en el momento en que se escribe el libro, está en el último lugar de la tabla. 4. Una vez descargado, deberíamos colocarlo en la carpeta /usr/local/src. 5. Ahora hacemos algo como esto:

Instalando Node.js

7

Descomprimiendo e instalando Node.js 1 2 3 4 5

tar -zxf node-v0.10.5.tar.gz cd node-v0.10.5 ./configure make sudo make install

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

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

¿Qué acabamos de instalar? Seguramente pienses que me he vuelto loco… ¿No estabamos instalando Node.js? ¿Por qué me haces esa pregunta? ¹⁰https://github.com/joyent/node/wiki/Installation

8

Instalando Node.js

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

node --version

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

v0.10.11

¡Bien! Ahora podemos seguir.

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

Figura 2: Consola de Node.js en Windows

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

> Di "Hola Mundo" ...

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 2

¡Hola mundo! undefined

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

parseInt('30',10);

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

¡Hola mundo!

10

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

$ mkdir holamundo $ cd holamundo/

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

$ touch app.js

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

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

Guárdalo, y en el terminal escribe: 1

$ node app.js

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

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

¡Hola mundo!

11

Hola mundo en el servidor 1

var http = require('http');

2 3 4 5 6

var server = http.createServer(function (request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.end("¡Hola Mundo!\n"); }).listen(8080);

7 8

console.log('Servidor escuchando por el puerto 8080');

Si ahora lo ejecutas con 1

$ node app.js

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

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

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

¡Hola mundo!

12

request, que es la petición, y response que es la respuesta que tenemos que enviar. Esa función será

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

¿Qué pasaría si no llamáramos a end? Si no llamáramos al método end de la respuesta, la petición nunca terminará y el navegador acabará por dar un error porque sigue esperando que le enviemos datos. ¿No me crees? ¡Pruébalo y verás!

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

Función no anónima 1

var http = require('http');

2 3 4 5 6

function gestionaPeticion (request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.end("¡Hola Mundo!\n"); }

7 8

var server = http.createServer(gestionaPeticion).listen(8080);

9 10

console.log('Servidor escuchando por el puerto 8080');

O incluso este:

¡Hola mundo!

13

Función en variable 1

var http = require('http');

2 3 4 5 6

var gestionaPeticion = function (request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.end("¡Hola Mundo!\n"); }

7 8

var server = http.createServer(gestionaPeticion).listen(8080);

9 10

console.log('Servidor escuchando por el puerto 8080');

La función createServer recibe otra como parámetro y se encargará de ejecutarla cuando haya terminado su trabajo. Esto se conoce como callback. Es un patrón muy común cuando se hacen cosas asíncronas ya que, sabemos cuándo se comienza a ejecutar el código, pero no sabemos cuándo va a acabar. En Node.js, los callbacks por norma general tienen la siguiente estructura. El primer parámetro de un callback debe ser un error si lo hubiere, en caso contrario null. A continuación, le siguen el resto de parámetros. Espera Antonio, ¿entonces Node.js no está siguiendo su propio patrón? No es eso. Lo que realmente ocurre es que la función que estamos pasando más que un callback es algo que está a la escucha para cuando llega una petición al servidor. El servidor tiene sus propios callbacks internos a la hora de realizar tareas asíncronas y, si hay un error, se encargará de gestionarlo él por lo que nuestra petición debería llegar sin más. En la siguiente línea, le decimos por el puerto por el que escuchar, en este caso el 8080. Puedes elegir cualquier otro puerto que no esté en uso. ¿Quién soy yo para decirte que no uses el puerto 6969 para tu desarrollo? ¡Nadie! Como verás, la llamada está encadenada con createServer. Eso es lo mismo que poner justo debajo… 1

server.listen('8080');

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

¡Hola mundo!

¿Te has fijado? Si eres observador habrás visto que detrás de createServer tenemos una llamada a la función listen. Esto se conoce como encadenamiento de funciones y se logra devolviendo siempre el objeto creado, en este caso el servidor. Este tipo de comportamiento se ha hizo especialmente común cuando apareció jQuery.

14

La consola de Node Si habéis estado atentos, habréis visto que hemos estado usando console.log y que, la información que hemos puesto, aparecía en el terminal mientras ejecutábamos la aplicación. Si has trabajado con JavaScript (cosa que espero) quizá ya estés acostumbrado a console puesto que es una forma de mostrar en la consola del Navegador información para ver por dónde va pasando nuestro código. console sirve para escribir en la salida estándar y en la de error. Ésta nos ayudará enormemente en la

tarea de saber qué es lo que está pasando en las entrañas de nuestra aplicación sin ser excesivamente complicado como podría ser el hecho de activar un depurador. Aunque no es algo de lo que, a priori, tengamos que preocuparnos, cabe destacar que escribir en la consola es un método síncrono por lo que, si abusamos brutalmente de ella, estaremos creando bloqueos en nuestra aplicación. Hay librerías estupendas para registro de eventos y logging en general para Node, como winston¹¹. No obstante, para el propósito de este libro nos valdremos de las bondades de la consola ya que cubre todas las necesidades de aprendizaje.

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

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

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

var planetas = 8;

2 3

console.log('Hay ' + planetas + ' planetas'); // Hay 8 planetas

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

16

La consola de Node 1

var planetas = 8;

2 3

console.log('Hay %d planetas', planetas); // Hay 8 planetas

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

var planetas = 8, expulsado = "Plutón";

3 4

console.log('Hay %d planetas',planetas,expulsado); // Hay 8 planetas Plutón

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

var planetas = 8, expulsado = "Plutón";

3 4 5 6

console.log('Hay %d planetas porque %s ya no es uno de ellos',planetas,expu\ lsado); // Hay 8 planetas porque Plutón ya no es uno de ellos

Como ves, ahora hemos puesto la cadena en su sitio. Vale, ¿pero qué es eso de %d y %s? Me alegra que lo preguntes… ¡oh espera! Esos valores son patrones que podemos usar para sustituir cadenas. ¡Hay más! Patrón %d %s %j

Tipo Enteros y coma flotante Cadenas Objetos JSON

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

La consola de Node 1 2 3 4 5 6

17

var sistema_solar = { planetas : 8, expulsarPluton : function() { ... } };

7 8 9

console.log(sistema_solar); //{ planetas: 8, expulsarPluton: [Function] }

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

console.error y console.warn Estas funciones son equivalentes y hacen lo mismo que log e info salvo que en vez de a la salida por defecto, lo envían a la de errores. ¿Qué es eso de la salida de errores? La salida de errores es una salida alternativa. Esto permite más flexibilidad puesto que puedes tener dos tipos de logs. Uno para errores (que serán importantes) y otro de información (más trivial). Esto es especialmente útil cuando tienes multitud de mensajes mostrándose por tu aplicación y tú solo quieres centrarte en los problemáticos.

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

console.time('Operación costosa'); // Aquí realizamos una… operación costosa console.timeEnd('Operación costosa'); timeEnd es como si paráramos ese cronómetro, miráramos el tiempo y lo anunciáramos al mundo.

Cuando se ejecute, veremos aparecer algo como:

La consola de Node 1

18

Operación costosa: 33ms

Esta es una forma realmente conveniente de saber dónde podemos tener algún cuello de botella en nuestro código.

Accediendo a las variables del entorno Quizá ni siquiera sepas lo que es una variable de entorno. No te avergüences. Es una cosa que hoy en día ya casi no se aprender ya que no son tan necesarias como antiguamente. No obstante, en sistemas Unix aun se usan bastante en desarrollo. Si no sabes lo que son, voy a intentar definirlas. Una variable de entorno es una variable que es almacenada por el Sistema Operativo y que puede afectar a la forma en que se ejecutan los procesos del sistema. Esto es, son variables que el sistema nos permite configurar y que pueden modificar cómo actúa en sí el sistema. Una de las variables más conocidas es el PATH, en la que se almacenan las rutas de los ejecutables. Si una ruta está definida en esa en los sistemas Unix por ejemplo. Si tienes una ruta en esa variable, podrás ejecutar una aplicación sin necesidad de ir a donde está ubicado. Definir una variable de entorno es realmente sencillo: 1 2 3 4

// Unix NUESTRA_VARIABLE="Su valor"; EXPORT NUESTRA_VARIABLE // Windows set NUESTRA_VARIABLE="Su valor"

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

NODE_ENV=production node app.js

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

Accediendo a las variables del entorno 1

var puerto = 80;

2 3 4 5 6 7 8 9 10

switch(process.env.NODE_ENV) { case 'production': puerto = 8080; break; case 'development': puerto = 8888; break; }

¡Recuerda! Las variables de entorno definidas de esta forma son temporales y, o bien desaparecen al cerrar la aplicación, o bien al reiniciar el equipo. Si quieres añadir las variables de forma permanente, tendrás que valerte de ∼/.bash_profile en sistemas Unix o cambiarlas en las propiedades del sistema en un entorno Windows.

20

Pasando parámetros a Node.js Además de valernos de las variables del entorno, podemos pasarles parámetros a Node.js de la siguiente forma: 1

$ node app.js [argumentos]

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

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

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

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

var argumentos = process.argv.splice(2); console.log(argumentos);

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

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

Pasando parámetros a Node.js

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

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

¹²https://github.com/substack/node-optimist

22

NPM - Node Packaged Modules Como ya hemos comentado, npm es instalado con Node.js de forma automática. Sí, sin que tengamos que hacer nada de particular. Lo bueno es que cuando actualicemos Node.js, npm lo haga también de forma automática. Vale Antonio, ¿pero me vas a explicar qué es eso de npm? npm es el gestor de paquetes de Node. Nos sirve para realizar cualquier actuación con paquetes de node. Buscarlos, instalarlos, actualizarlos, etc. Es posible que hayas utilizado algún gestor de paquetes con anterioridad. Los hay para varios sistemas: • • • •

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

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

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

$ npm search async

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

NPM - Node Packaged Modules

24

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

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

$ npm view abiogenesis

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

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

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

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

$ npm ls

Y veremos algo así:

NPM - Node Packaged Modules 1 2 3

25

/Users/antonio.laguna/projects/nodetest └─┬ [email protected] └── [email protected]

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

$ npm ls -g

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

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

$ npm install less

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

NPM - Node Packaged Modules 1

26

$ npm install less -g

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

Cómo decidir qué tipo de instalación realizar Piensa que, en general, una instalación global no es algo bueno. Seguro que, trabajando con JavaScript has leído que las variables globales son malas. Las instalaciones globales… también. Bueno, no es que sean malas, solo que es algo que no tendríamos que tomar a la ligera. Entonces, ¿cómo puedo decidir? Veamos unos consejos: • Si quieres instalar un paquete que funcione dentro de tu aplicación con require(), instálalo de manera local. • Si estás instalando un paquete que puede ser usado desde la línea de comandos como un programa o algún uso extendido (como en el caso de Express), instálalo de manera global. Algunos de los paquetes que suelen instalarse de manera global: • • • • • •

Express forever nodemon Bower Grunt Uglify-js

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

NPM - Node Packaged Modules

27

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

$ npm uninstall -g less

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

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

Dudas frecuentes

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

NPM - Node Packaged Modules

¿Cómo actualizo un paquete? Actualizar un paquete es realizar la misma tarea de instalación, es decir npm install {nombre del paquete} o npm install -g {nombre del paquete} si queremos actualizarlo globalmente. Asegúrate de revisar las notas de la versión antes de realizarlo, especialmente si es una aplicación en producción, ya que puede que hayan eliminado funciones o cambiado el funcionamiento de algo.

.

28

Cómo mantener Node.js actualizado Si has leído al principio del libro, ya sabrás que Node.js se actualiza con bastante frecuencia. A veces, incluso un par de veces a la semana. La pregunta lógica que surge es… ¿cómo mantengo Node.js actualizado? Ciertamente, podrías seguir los mismos pasos que hemos visto a la hora de instalar Node.js. Pero, si eres como yo, te parecerá un rollazo. No obstante, hay un método mejor y que yo suelo usar con bastante asiduidad. El paquete se llama n y tendremos que instalarlo de forma global ya que, en el fondo es un programa de la línea de comandos. 1

$ npm install -g n

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

$ n stable

Y él solo se encargará de hacer el resto. ¿No es genial? A mi personalmente me ahorra bastante tiempo. Estoy suscrito por Twitter a la cuenta de Node.js²¹ y, si leo que tienen una actualización solo tengo que lanzar el comando de arriba, esperar un ratillo y disfrutar de una nueva versión. Lamentablemente n no funciona con Windows. Más lamentablemente aun, en su Github no aparece ninguna incidencia al respecto por lo que parece que no tienen interés en arreglarlo. n tiene otros comandos que te pueden servir:

Comando

Acción

n n n n n n n n n

Instala o activa la última versión de node Instala o activa la última versión estable de node Instala y/o usa la de node especificada Ejecuta la con [args ...] Muestra la ruta de los ejecutables de la Elimina la(s) version(es) especificada(s) Muestra la última versión disponible de node Muestra la última versión estable de node Muestra las versiones de node disponibles

latest stable use [args ...] bin rm --latest --stable ls

²¹https://twitter.com/nodejs

Cómo mantener Node.js actualizado

30

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

Sobre las versiones de Node.js Cuando aparece una nueva versión de Node.js, aparece una noticia en el blog de Node.js o en su Twitter. Concretamente puedes dirigirte al apartado [release] (http://blog.nodejs.org/release/) para ver qué es lo que han sacado. Cada versión viene con la cadena al lado (Stable) o (Unstable) junto a una escueta explicación de los cambios introducidos. En la mayoría de las ocasiones no nos aportará mucha información ya que suelen ser mejoras de rendimiento y errores corregidos. Pero tendrás que dar un salto de fe. Pensar que lo estable es realmente estable… e instalarlo. A veces, cuando ocurre un cambio de versión mayor como el que ocurrió al saltar de la versión 0.8 a la 0.10, podemos encontrar un artículo “mucho” más detallado²² e incluso una página en GitHub²³ donde nos detallan las diferencias entre versiones. ¡Recuerda visitar siempre el blog de Node.js antes de realizar ninguna actualización de versión! ²²http://blog.nodejs.org/2013/03/11/node-v0-10-0-stable/ ²³https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10

Nuestra primera aplicación de Node.js Ahora que ya tenemos conocimientos básicos, vamos a ponernos manos a la obra… ¿no? Recuerdo cuando empecé a estudiar programación y todo el mundo le preguntaba a la profesora: ¿cuándo vamos a poder programar? ¿para qué tanta teoría? Así que, no me enrollo más, pongámonos manos a la obra. Vamos a hacer una sencilla calculadora, nada del otro mundo, pero nos ayudará a ir abriendo boca y a aplicar casi todo lo que hemos visto hasta ahora. Ten en cuenta que este no es el objetivo de Node.js pero no quiero complicar mucho este primer capítulo.

Código de la calculadora 1 2 3 4 5

var argumentos = process.argv.splice(2), operacion = argumentos[0], valor1 = parseInt(argumentos[1],10), valor2 = parseInt(argumentos[2],10), nombreOperacion, resultado;

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

if (operacion === undefined || valor1 === undefined || valor2 === undefined\ ) { console.error('Alguno de los parámetros no ha sido especificado.'); } else { switch (operacion) { case '*' : { nombreOperacion = 'multiplicación'; resultado = valor1 * valor2; break; } case '+' : { nombreOperacion = 'suma'; resultado = valor1 + valor2; break; } case '-' : { nombreOperacion = 'resta'; resultado = valor1 - valor2;

Nuestra primera aplicación de Node.js

32

break; } case '/' : { nombreOperacion = 'división'; if (valor2 !== 0){ resultado = valor1 / valor2; } else { console.error('¿Qué querías? ¿¡Cargarte el universo?!') } break; } default : { console.error('La operación %s no ha sido implementada', operacion); break; }

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

}

42 43

if (resultado !== undefined){ console.log('El resultado de la %s es %d', nombreOperacion, resultado); }

44 45 46 47

}

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

Nuestra primera aplicación de Node.js 1 2 3 4 5 6

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

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

Ejercicio Intenta añadir una nueva operación: la potencia en el que el primer parámetro sea la operación, el segundo el valor a elevar y el tercero la potencia que queremos usar. ¿Hay que hacer alguna validación especial?

33

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

Adentrándonos en Node.js Ahora que ya tenemos los conceptos básicos sobre Node.js, podemos empezar a jugar con conceptos y funciones que son más complejas. Estoy seguro de que es lo que estabas deseando, ¿verdad? A nadie le gustan las presentaciones, son aburridas y odiosas pero, al menos en este caso, estaba totalmente justificada. ¡Lo prometo! Este capítulo podemos considerarlo como la planta baja de nuestra casa, ya que sobre todos estos fundamentos comenzaremos a profundizar en la tercera parte cuando nos metamos en faena con Express. Basta de cháchara. ¡Vamos a ponernos manos a la obra!

Gestión de dependencias con

package.json Node.js implementa CommonJS para distribuir paquetes lo cual ayuda a que todo este proceso esté bastante extendido. Esto hace que, si creas una aplicación, la puedas distribuir fácilmente a través de npm para que pueda ser instalada por cualquiera. Pero hay una cosa que tienes que tener en cuenta. Tu aplicación puede depender por ejemplo de Express, pero Express a su vez, tiene una serie de dependencias que necesita para funcionar, que a su vez… ¡pueden necesitar más dependencias! No obstante, queremos evitar a toda costa el tener que bajarnos las dependencias manualmente y, lo que es aun peor, sus versiones. Es por ello, que a la hora de distribuir un paquete a través de npm, viene sin la carpeta node_modules en la cual se instalan todas las dependencias. Es probable que te estés preguntando… Si Express usa como dependencia a connect, ¿puedo hacer yo require('connect')y usarlo yo también? Realmente no. Tampoco es buena idea. La gestión de dependencias en cadena que usa Node.js (y la mayoría de los lenguajes), está pensada así para que las versiones no den conflictos entre si. Voy a intentar explicarlo con un ejemplo más gráfico. Imagina por un momento, que eres carpintero. La programación es muy complicada y has decidido buscar fortuna con otra profesión. Te has bajado de Google un diseño de rueda de carruaje espectacular así que, diseñas tu carruaje alrededor de ese diseño de rueda, para que encaje perfectamente. Hasta ahora todo bien. Pero decides llevar tu carruaje a una feria para mostrarlo a posibles compradores. En el camino, se te rompe una rueda del carruaje. Un compañero que pasa cerca, se ofrece a prestarte una de sus ruedas. Desmontas la tuya y… nada. No entra la nueva rueda. Tu carruaje depende de la versión de la rueda que te descargaste de Google. La otra no funciona. Ciertamente podrías hacer algunos ajustes a tu carro y usar la otra rueda, pero no es lo ideal. Por ello, si realmente quieres, puedes usar connect de Express usando algo así: 1

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

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

Gestión de dependencias con package.json

37

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

Versionado semántico Antes de meternos con el archivo en sí, me gustaría hablar del versionado semántico (semver), que es usado tanto por Node.js como por los paquetes y tendremos que usarlo en cuanto definamos las dependencias. Siguiendo nuestro ejemplo, sería la forma de catalogar la versión de las ruedas. Cuentan las leyendas, que en tiempos ancestrales no existía esta forma de versionar el software. Todos recordaremos la famosa etiqueta Beta de Gmail que, en aquel momento quería decir: “¡Esto funciona pero vamos a añadir más cosas y eventualmente puede romperse!” (eso no es lo que hoy en día significa Beta). Además, teníamos las versiones Alpha que estaban en un estado anterior a Beta. No obstante, es útil porque permite a la empresa probar el software y ofrecer feedback sobre cosas como Interfaz, Velocidad y Usabilidad. Hoy en día, las cosas han cambiado un poco conforme las metodologías han ido avanzando. Por ello, también lo ha hecho la forma en que ponemos las versiones de las versiones (válgame la redundancia). Hoy en día, lo que está de moda son las versiones del tipo x.y.z. Si no te gusta la moda no te preocupes. Esta moda ha llegado por un motivo y es el hecho de darle significado a las versiones (de ahí el nombre de versionado semántico). • x - Cambio mayor : Este tipo de cambio es un cambio serio en la aplicación de manera que es drásticamente diferente de anteriores versiones. Normalmente, si esta fuera una aplicación de pago, es el tipo de cambio que te costarían dinero. • y - Cambio menor : Has añadido algo nuevo que mejora significativamente el producto o bien has cambiado algo que estaba causando muchos problemas y/o era malicioso o si estás eliminando algo. Han de ser retrocompatibles con lo anterior. • z - Parche : Correcciones de errores. Han de ser retrocompatibles con lo anterior. En Node.js y en los paquetes, esta es la metodología que se usa para versionar cada uno de los paquetes que podemos instalar con npm. No obstante, aceptan una modificación, el - por lo que este tipo de versión 0.30.1-2 es válida para paquetes npm, pero no en el mundo semver. A mi personalmente este añadido me parece superfluo ya que, nada te impide aumentar la versión z para hacer ajustes, ¿no? En general, no deberías usar 1.X.X a no ser que la aplicación sea estable. Seguro que dudas de si tu aplicación es lo suficientemente estable como para ponerle ese 1.0.0 que tanto gusta. Quizá algo que te ayude es hacerte esta otra pregunta: ¿Está la aplicación en producción?

Gestión de dependencias con package.json

38

Si has respondido sí, es que ya debería haber pasado a esa versión así que… ¡Corre! ¿Y no está Node.js en producción? Hombre… no nos pongamos quisquillosos. En realidad, Node.js no es que esté en producción si no que se usa para aplicaciones que se ponen en producción. No obstante, podríamos considerarlo como “en producción”. Lo que ocurre, es que está en continuo cambio y hay muchas partes del código que aun no funcionan todo lo estable que le gustaría al equipo que está detrás de su desarrollo. ¿Y qué pasa con las versiones Alpha y Beta ahora? No te preocupes, aun no han desaparecido. Puedes usarlas si es que quieres y, en cierta manera, le añaden al código más significado. Para crearlas, solo tienes que añadir al final la versión que quieras. Por ejemplo 0.1.2-3-beta o 0.1.2-4-alpha. Aquí tienes un ejemplo de cómo evalúa node semver las versiones: 1

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

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

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

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

Gestión de dependencias con package.json 1

{ "name": "mi-aplicacion", "description": "Un paquete de prueba que nunca verá la luz", "author": "Uno que aprende ", "version" : "0.0.1", "dependencies": { "express": ">=3.2.0" }, "engine": "node 0.10.x"

2 3 4 5 6 7 8 9 10

39

}

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

npm install

Y verás como automáticamente empieza a bajarse las dependencias indicadas. En este caso solamente Express. Node intenta buscar la versión más actualizada posible que cumpla con los requisitos que hemos puesto para cada paquete. Si por lo que sea no hay versión del paquete que cumpla con los requisitos, la instalación fallará. Aunque hayamos indicado que queremos las versiones mayores a 3.2.0 esto no significa que sea una buena práctica. Realmente estamos diciendo, quiero estar a la última y, cada vez que ejecutemos el anterior comando nos traeremos la última versión de la dependencia… genial, ¿no? No El día que por algún casual salga Express 4.0.0 es muy probable que tu aplicación deje de funcionar porque los cambios no tienen por qué ser retrocompatibles. Así que recuerda tener mucho cuidado con todo esto. Recuerda que… Las versiones sin control, no sirven de nada…

Gestión de dependencias con package.json

40

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

{ "name": "mi-aplicacion", "description": "Un paquete de prueba que nunca verá la luz", "author": "Uno que aprende ", "version" : "0.0.1", "dependencies": { "express": ">=3.2.0" }, "devDependencies": { "mocha": "1.11.X" }

2 3 4 5 6 7 8 9 10 11 12

}

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

$ npm install --production

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

$ npm install --dev

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

Gestión de dependencias con package.json 1 2

41

$ npm install node-mysql --save $ npm install mocha --save-dev

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

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

²⁵http://package.json.nodejitsu.com/

Exportando en Node.js Hasta ahora hemos estado importando “paquetes” a nuestra aplicación ciégamente. Por ejemplo: 1

var util = require('util');

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

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

tweets.js 1

// var exports = {};

2 3 4 5

var misTweets = ['Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4'], misOtrosTweets = ['Me encanta escuchar Justin Beaver', 'Me gusta Crepúscu\ lo'];

6 7 8 9 10

exports.tweets = misTweets; exports.cuentaTweets = function() { return misTweets.length + misOtrosTweets.length; };

11 12 13 14 15

// exports = { // tweets : misTweets, // cuentaTweets : function() { ... } // };

Exportando en Node.js

43

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

app.js 1

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

2 3 4

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

5 6 7

console.log(tweets.cuentaTweets()); // 4

8 9 10

console.log(tweets.misOtrosTweets); // undefined

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

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

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

Exportando en Node.js

44

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

Entonces, si en el ejemplo anterior ponemos esto:

tweets.js 1 2 3 4

module.exports = 'MIS TWEETS'; exports.cuentaTweets = = function() { return 4; };

Y ahora hacemos esto:

app.js 1

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

2 3 4

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

Como ves, nuestra inútil aplicación ha obviado por completo la función cuentaTweets ya que module.exports había tenido lugar. module.exports puede tomar el valor de cualquier cosa de JavaScript: ya sea una función, una matriz

o una “clase”:

tweets.js 1 2 3

function Tweets (cuenta) { this.cuenta = cuenta; }

4 5 6 7

Tweets.prototype.leerTweets = function() { console.log('Leyendo tweets de "%s"',this.cuenta); }

8 9

module.exports = Tweets;

Exportando en Node.js

45

app.js 1

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

2 3 4

var belelros = new Tweets('@Belelros'), funcion13 = new Tweets('@Funcion13');

5 6 7

belelros.leerTweets(); // Leyendo tweets de "@Belelros" funcion13.leerTweets(); // Leyendo tweets de "@Funcion13"

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

Algunas aclaraciones Cabe destacar varias cosas para evitar confusiones: • Las llamadas a exports o module.exports han de ser inmediatas en el código, no pueden estar dentro de un callback ni nada parecido. Esto no funcionará por ejemplo:

1 2 3

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

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

1 2 3

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

Es idéntico a esto:

Exportando en Node.js 1 2 3

46

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

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

Eligiendo el método adecuado En este momento estarás teniendo un cacao mental y estarás pensando ¿y cuándo elijo uno u otro? Yo he estado ahí. La verdad es que es cuestión de preferencias porque realmente, module.exports es exactamente lo mismo que exports. No obstante, para evitar confusiones, lo mejor es quedarse con module.exports. Las reglas son las siguientes: • Si vamos a crear una clase y queremos exportarla, lo ideal es definir la clase, sus prototipos y luego asociarla directamente a module.exports.

tweets.js 1 2 3

function Tweets (cuenta) { this.cuenta = cuenta; }

4 5 6 7

Tweets.prototype.leerTweets = function() { console.log('Leyendo tweets de "%s"',this.cuenta); }

8 9

module.exports = Tweets;

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

Exportando en Node.js

47

config.js 1 2 3 4 5

module.exports = { usuario : 'root', clave : 'root', servidor : 'localhost' };

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

bdatos.js 1 2 3 4 5

var config = { usuario : 'root', pass : 'root', servidor : 'localhost' };

6 7 8 9 10

module.exports = function(opciones) { var usuario = opciones.usuario || config.usuario, pass = opciones.pass || config.pass, servidor = opciones.servidor || config.servidor;

11 12 13 14 15 16 17 18

return { consulta : function() { console.log('Haciendo consulta al servidor %s con el usuario %s', ser\ vidor, usuario); } } };

Exportando en Node.js

48

app.js 1 2

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

3 4 5 6 7

local.consulta(); // Haciendo consulta al servidor localhost con el usuario Trololo remoto.consulta(); // Haciendo consulta al servidor remoto con el usuario root

En bdatos creamos un objeto con las variables de conexión por defecto y luego exportamos la función. La función es una de esas joyas de JavaScript: una función que devuelve funciones ¡dawg²⁶! Básicamente estamos protegiendo el valor de las variables y asegurándonos de que si no pasamos alguno de los parámetros, tengamos un valor por defecto. Esta función a su vez devuelve un objeto (que es lo que se queda al final), con una función consulta que se encarga de realizar la supuesta consulta a la base de datos. En app.js requerimos 2 veces el mismo archivo, pero pasando diferentes opciones para obtener diferentes resultados. Como ves, es realmente sencillo exportar código que pueda ser reusado por otras partes de la aplicación, lo cual ayuda enormemente a que el código sea más sencillo de mantener. ²⁶http://i.imgur.com/TWmBWl8.jpg

Organizando el código de nuestra aplicación La forma en que organicemos el código de nuestra aplicación es bastante subjetiva y suele ser algo de preferencia personal. No obstante recuerdo que, cuando comencé con Node.js, todo el código estaba en app.js y en principio todo parecía funcionar. Pasa el tiempo y la aplicación crece y quieres reusar código o cambiar algo y poco a poco tu dedo del scroll del ratón empieza a quejarse… ¡estás haciendo mucho scroll por el código! Es en esos momentos cuando uno decide que hay que comenzar a separar las cosas en archivos y poner cada una su lugar. Como digo, es algo de preferencia personal y ningún módulo de Node.js te fuerza a usar ninguna estructura en particular. No hay ninguna buena estructura reconocida aunque con un poco de buenas prácticas y experimentación, te harás con una estructura que se ajuste a tus necesidades. Sin embargo, en este libro voy a plasmar una estructura que yo considero buena. La idea principal que has de tener en mente conforme escribes el código es, ¿cómo podría separar esto? ¿es esto un módulo con el que usaría require para traer esa funcionalidad? Si la respuesta a esa pregunta es sí, deberías buscar primero si ya hay alguna librería en el mercado, que te pueda servir. En caso de que no, porque sea código particular a tu aplicación, tendrás que escribir la tuya propia y más adelante, si lo crees necesario, podrías publicarla o bien mantenerla para ti. ¡Pero basta de literatura! En la siguiente imagen puedes ver una imagen de una estructura que yo considero adecuada para comenzar a organizar nuestra aplicación.

Figura 3: Estructura de carpetas

Como ves es una estructura muy sencilla aunque cuando veamos Express se complicará un poco. Veamos las carpetas una a una. • models : En esta carpeta guardaremos los modelos de nuestra aplicación, por ejemplo Tweet o Usuario. • lib : En esta carpeta nos encargaremos de guardar todas las librerías que creemos. Y por librerías nos referimos a funcionalidades de la aplicación en concreto.

Organizando el código de nuestra aplicación

50

– db : Aquí guardaremos el código relacionado con la conexión a la base de datos, sea esta MongoDB o MySQL o lo que sea. • config.js : Aquí guardaremos la configuración de la aplicación, normalmente en un objeto sencillo y, habitualmente, diferenciando si la configuración es en producción o en desarrollo. • app.js : Este archivo es el encargado de que todas las piezas funcionen, de hacer de pegamento e iniciar todos los procesos necesarios. Vamos a hacer un ejercicio de imaginación para poner esto un poco más en contexto. Imaginemos que nuestra aplicación es un panel para mostrar tickets de soporte de nuestra empresa y vamos a mostrar correos y tweets. A grosso modo, tendremos un modelo llamado Correo y otro que se llame Tweet. Igualmente, tendremos una librería que se encargue de leer tweets y otra de leer correos por lo que tendremos un LectorCorreo y otra que sea LectorTwitter. Como son bastante complejas, las metemos en sus respectivas carpetas para añadir más módulos a esa librería como: TweetParser o EmailParser.

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

config.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14

module.exports = { 'production' : { 'bdatos' : 'miaplicacion', 'usuario' : 'root', 'password' : 'lT10Vw8H', 'debugging' : false }, 'development' : { 'bdatos' : 'test', 'usuario' : 'root', 'password' : '', 'debugging' : true } };

Organizando el código de nuestra aplicación

51

app.js 1 2

var config = require('./config'); config = config[process.env.NODE_ENV] || config['development'];

3 4

console.log(config);

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

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

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

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

global.config = require('config');

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

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

Organizando el código de nuestra aplicación

52

Pasando parámetros A mi modo de ver, no tiene mucho sentido que, si creas un módulo para leer correos, el módulo sea capaz por sí mismo de decidir a qué cuenta de correo ha de conectarse leyendo un archivo de configuración. Esto empeora notablemente la capacidad que tiene tu código de ser reutilizable (porque necesitarías un archivo con unos datos concretos). Por ello, lo mejor es pasar los datos necesarios o bien al requerir el módulo (como ya hemos visto) o bien al crear una instancia de la clase que sea.

Emisión de eventos con

EventEmitter Muchas de las clases de Node.js emiten eventos. Este es sin duda uno de los motivos por los que Node.js es tan rápido, ya que en vez de leer archivos o bases de datos con cada petición, una vez que comienza el servidor, se declaran las funciones y las variables y todo queda a la espera de que ocurra un evento. Si estás acostumbrado a usar jQuery, este tipo de comportamiento no te será del todo desconocido, ya que es muy habitual asociar funciones a eventos que ocurren en el DOM, como un click de un botón, cuando pulsamos una tecla, etc. Los eventos forman parte del núcleo de Node.js y vamos a intentar ver cómo los interpreta Node. Antes, me gustaría hablar un poco sobre el patrón del Observador, sobre el cual se basan los eventos.

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

Emitiendo eventos con Node.js Veamos un breve ejemplo:

Emisión de eventos con EventEmitter 1 2

54

var events = require('events'); var canalDeTwitter = new events.EventEmitter();

3 4 5 6

canalDeTwitter.on('retraso',function(){ console.log('Avisando al jefe…'); });

7 8

canalDeTwitter.emit('retraso');

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

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

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

Pasando parámetros a los eventos Vale, hasta ahora solo sabemos que ha habido un retraso pero… ¿qué lo ha causado? ¿Cómo puedo recibir un mensaje? Es muy sencillo: 1 2 3 4

canalDeTwitter.on('retraso',function(mensaje){ console.log('Avisando al jefe del aviso ""%s" enviado por "%s"', mensaje.\ mensaje, mensaje.autor); });

5 6 7 8 9

canalDeTwitter.emit('retraso', { 'mensaje' : 'Un tren se ha estropeado', 'autor' : 'La compañía de metro' });

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

Emisión de eventos con EventEmitter

55

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

canalDeTwitter.removeListener('retraso', nombreFuncion);

O podemos eliminarlos todos a la vez: 1

canalDeTwitter.removeAllListeners('retraso');

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

Hola mundo en el servidor 1

var http = require('http');

2 3 4 5 6

var server = http.createServer(function (request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.end("¡Hola Mundo!\n"); }).listen(8080);

7 8

console.log('Servidor escuchando por el puerto 8080');

Esto es exactamente lo mismo que poner:

Emisión de eventos con EventEmitter

56

Hola mundo en el servidor con eventos 1

var http = require('http');

2 3

var server = http.createServer().listen(8080);

4 5 6 7 8

server.on('request',function (request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.end("¡Hola Mundo!\n"); });

9 10

console.log('Servidor escuchando por el puerto 8080');

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

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

var events = require('events');

2 3 4 5

function ClienteTwitter (canal) { this.canal = canal; }

6 7 8 9 10

ClienteTwitter.prototype.obtenerTweets = function() { this.emit('retraso', 'Ha ocurrido un retraso, anunciado en el canal "' + \ this.canal + '"'); }

11 12

// Aquí colocaremos el código que veremos más abajo

13 14

var metroMadrid = new ClienteTwitter('@metro_madrid'),

Emisión de eventos con EventEmitter 15

57

autobusesMadrid = new ClienteTwitter('@emtmadrid');

16 17 18 19

function registrarMensaje(mensaje) { console.log(mensaje); }

20 21 22

metroMadrid.on('retraso', registrarMensaje); autobusesMadrid.on('retraso', registrarMensaje);

23 24 25

metroMadrid.obtenerTweets(); // Ha ocurrido un retraso, anunciado en el can\ al...

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

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

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

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

var util = require("util");

2 3 4 5 6

function ClienteTwitter (canal) { this.canal = canal; events.EventEmitter.call(this); }

7 8

util.inherits(ClienteTwitter, events.EventEmitter);

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

Emisión de eventos con EventEmitter

58

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

var http = require('http');

2 3 4 5 6 7

var opciones = { hostname : 'isitchristmas.com', port : 80, method : 'GET' }

8 9

var peticion = http.request(opciones);

10 11 12 13 14 15 16 17 18

peticion.on('response', function(respuesta) { respuesta('data', function (trozo) { console.log('BODY: ' + trozo); }); }); peticion.on('error', function(e) { console.log(e.message); });

19 20

peticion.end();

Si ejecutas esto en Node, verás que en pantalla aparece lo mismo que si abres la página y revisas el código de la misma. Pero… ¿qué es lo que está ocurriendo? Lo que hacemos es crear una peticion a la que le pasamos una serie de parámetros. En nuestro caso la página que queremos obtener, el puerto (80 por defecto) y el método, que en este caso es GET. peticion es un emisor de eventos y en este caso estamos atentos a dos eventos: 1. response es cuando obtenemos una respuesta por parte del servidor y nos pasa un parámetro con la respuesta en sí, que es a su vez otro emisor de eventos. Node hace esto así para que la lectura del cuerpo sea asíncrona y la vaya parseando por trozos. Cada vez que un trozo está disponible, emite un evento data que aprovechamos para pasarlo a la consola.

Emisión de eventos con EventEmitter

59

2. error es cuando… ¡ocurre un error! Estoy seguro de que no lo adivinaste. En cualquier caso, anunciamos el error. Finalmente, peticion.end() es lo que hace que realmente se ejecute la petición y, eventualmente, se dispararán los eventos. Como ves, es realmente sencillo y es algo que está presente en muchos de los módulos de Node.js

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

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

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

var Stream = require('stream'); var stream = new Stream;

Vamos a ver cómo usarlos.

61

Los Streams en Node.js

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

origen.pipe(destino);

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

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

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

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

Figura 4: Eventos del Stream de Lectura

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

Los Streams en Node.js 1 2

62

var util = require('util'), Stream = require('stream');

3 4 5

function MiStreamDeLectura(array) { var self = this;

6

Stream.Readable.call(this, { objectMode: true });

7 8

this._read = function (tamano) { array.forEach(function(elemento){ self.push(elemento + '\n'); }); this.push(null); }

9 10 11 12 13 14 15

}

16 17 18 19 20

util.inherits(MiStreamDeLectura, Stream.Readable); var misTweets = ['Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4']; var stream = new MiStreamDeLectura(misTweets); stream.pipe(process.stdout);

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

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

La siguiente línea es en la que llamamos al constructor original de Stream.Readable sobre nuestra actual clase. Esta es la forma que tenemos en JavaScript de llamar al constructor de la clase que heredamos, no es que sea muy sexy ya que no es algo como lo que haríamos en PHP:

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

Los Streams en Node.js 1 2 3 4 5 6 7

63

class MiStreamDeLectura extends StreamReadable { function __construct() { parent::__construct(); } }

o Java: 1 2 3 4 5

public class MiStreamDeLectura extends StreamReadable { public MiStreamDeLectura() { super(); } }

En JavaScript tenemos que indicar claramente el constructor de qué clase vamos a usar. Con call pasamos el argumento this que queremos que use que, en este caso es el propio objeto. Al constructor de Stream.Readable le pasamos una opción objectMode que, si es true, indica que el Stream debe comportarse como un flujo de objetos y no un buffer. En la práctica, casi siempre querremos esto ya que indica que stream.read(n) devuelve un único valor y no un Buffer de tamaño n. La función _read tan solo se encarga de recorrer la matriz con la función forEach y va añadiendo los datos al Stream a través de push con un retorno de carro al final para que la salida sea más bonita. Si os fijáis, hemos ignorado el valor de tamano ya que es una recomendación, no una obligación. Cuando terminamos el bucle, hacemos push(null). Esto le indica al Stream que hemos llegado al final y que ya no va a recibir más datos por lo que está listo para cerrarse. Luego hacemos que la clase que hemos creado, MiStreamDeLectura, herede de Stream.Readable. Además, creamos una matriz de relleno que nos sirve para ilustrar el ejemplo. Finalmente, usando la función pipe, redirigimos los datos del Stream hacia process.stdout que no es más que el terminal y lo cual permite que veamos el resultado por la pantalla. Si lo ejecutamos, verás que aparecen por orden todos los “tweets” en la pantalla.

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

64

Los Streams en Node.js

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

Figura 5: Eventos del Stream de Escritura

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

Los Streams en Node.js 1 2

65

function MiStreamDeEscritura() { this.valor = "";

3

Stream.Writable.call(this);

4 5

this._write = function (trozo, codificacion, callback) { this.valor += trozo.toString(); callback(); };

6 7 8 9 10

}

11 12

util.inherits(MiStreamDeEscritura, Stream.Writable);

13 14 15 16 17 18

var misTweets = ['Tweet 1', 'Tweet 2', 'Tweet 3', 'Tweet 4']; var escritura = new MiStreamDeEscritura(); escritura.on('finish',function(){ console.log(this.valor); });

19 20 21

var lectura = new MiStreamDeLectura(misTweets); lectura.pipe(escritura);

Antes de nada, decir que la implementación de este Stream es bastante trivial ya que únicamente guardamos el valor en memoria, pero podría guardarlo en disco o en una base de datos, o en cualquier otro sitio. En el constructor de nuestra clase, inicializamos el valor a una cadena vacía para poder rellenarla con lo que vayamos recibiendo en nuestro Stream. Esta vez, no le pasamos nada al constructor de Writable como lo hicimos con Readable. La implementación de _write no esconde complejidad alguna ya que únicamente vamos encadenando el trozo que convertimos a cadena con la función toString a valor. En nuestro caso, como ya hemos almacenado el dato, llamamos a callback (por si la hubiera), sin pasar ningún error ni parámetro ya que la implementación es bastante simple. Después asociamos una función al evento finish para que podamos saber que ha acabado y poner en consola el contenido de la variable valor. Si te has fijado, no hemos tenido que llamar a write() ni a end() nosotros si no que, al ser Streams, pipe() sabe lo que tiene que hacer conforme va transmitiendo datos sin que tengamos que hacer nada de particular.

Los Streams en Node.js

66

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

Transformación - Transform Este tipo de Streams nos sirven para transformar datos: Entran datos y salen datos ligeramente transformados. Se considera buena práctica el no transformar los datos en ninguno de los Streams anteriormente explicados y hacerlos en uno de estos. Supongamos que queremos transformar un XML a JSON y guardarlo, lo mejor sería tener 3 Streams: 1. El primero de lectura, en el que entran los datos 2. El segundo, de transformación, que se encarga de transformar cada nodo, en un objeto JSON 3. El último, de escritura, encargado de guardar los datos Para hacer que tu clase sea un Stream Transform, ha de heredar de la clase stream.Transform e implementar el método _transform(trozo, codificacion, callback) que, como ves, es esencialmente el método _write de los Streams Writable. Vamos a ver cómo implementamos este tipo de Stream para hacer que al pasarle una matriz de números, nos devuelva la enésima potencia del número: 1 2

function MiStreamDeTransformacion(potencia) { Stream.Transform.call(this, { objectMode: true });

3

this._transform = function (trozo, codificacion, callback) { var numero = parseInt(trozo,10); this.push(Math.pow(numero,potencia).toString() + '\n'); callback(); };

4 5 6 7 8 9

}

10 11

util.inherits(MiStreamDeTransformacion, Stream.Transform);

12 13 14 15

var numeros = [1,2,3,4]; var escritura = new MiStreamDeEscritura(); var cuadrado = new MiStreamDeTransformacion(2);

Los Streams en Node.js 16 17 18

67

escritura.on('finish',function(){ console.log(this.valor); });

19 20 21

var lectura = new MiStreamDeLectura(numeros); lectura.pipe(cuadrado).pipe(escritura);

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

Pasarela - PassThrough El Stream del tipo PassThrough es una subclase de Transform y… a ver como lo digo… ¡no hace nada! Tan solo pasa los datos de la entrada a la salida. Es habitual implementar este tipo de Streams como una pasarela para “espiar” los datos del Stream, especialmente para hacer pruebas.

El sistema de archivos La lectura y escritura de datos siempre es una fuente de problemas en los programas. Las operaciones contra el disco duro son realmente lentas lo cual suele provocar que el programa no haga nada durante ese período de tiempo. La principal idea de Node.js fue la de eliminar esa barrera y hacer que la lectura y escritura de datos en el disco no fuera tan dramático al convertirlas en tareas asíncronas.

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

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

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

prueba.txt 1

Lisa necesita un aparato... ¡Seguro dental!

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

²⁸http://nodejs.org/api/fs.html

El sistema de archivos 1

69

var fs = require('fs');

2 3 4 5 6 7 8 9 10

fs.readFile('prueba.txt', function(err,data){ if (err){ console.error(err); } else { console.log(data); } });

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



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

var fs = require('fs');

2 3 4 5 6 7 8 9 10

fs.readFile('prueba.txt', {encoding:'utf8'}, function(err,data){ if (err){ console.error(err); } else { console.log(data); } });

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 1

70

var fs = require('fs');

2 3 4 5 6 7 8 9 10

fs.writeFile('homer.txt', 'Seguro dental', function(err){ if (err){ console.error(err); } else { console.log(data); } });

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

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

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

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

El sistema de archivos 1

71

var fs = require('fs');

2 3 4

var lectura = fs.createReadStream('prueba.txt'); var escritura = fs.createWriteStream('homer.txt');

5 6

lectura.pipe(escritura);

Como ves, hemos creado un Stream de Escritura con createWriteStream que recibe las mismas opciones que su análogo, y lo hemos enchufado al Stream de Lectura. Dado que ambos Streams tratan con buffers, no necesitamos codificar la información ya que va directamente a un archivo copiando los datos de origen al destino. Si abres ahora el archivo homer.txt, verás que el contenido es completamente idéntico al que hay en prueba.txt. ¿¡A qué es fácil?!

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

Introducción a Express Ahora que ya tenemos unas nociones básicas de cómo funciona Node.js y de para qué sirve, estamos preparados para dar el siguiente paso en nuestro particular camino de iluminación. Así que, ¡cojamos el Express-o y sigamos aprendiendo! Vale vale… chiste malo. Llevamos mencionando a Express todo el libro, en estos momentos debe ser como cuando tu madre te habla de tus primos los del pueblo, los conoces de oídas pero no los conoces realmente. Vamos a intentar cambiar eso. Express es un framework que te ayudar a organizar tu aplicación, siguiendo la arquitectura ModeloVista-Controlador en el servidor. En realidad no te fuerza a gran cosa, pero te ayuda. No hay modelos ni nada por el estilo. Express se inspiró en Sinatra (un framework de Ruby) así que si, por algún casual lo habéis usado alguna vez, su uso resultará natural. La característica más potente de Express (a mi modo de verlo) es la versatilidad que te ofrece a la hora de gestionar las rutas de nuestra aplicación.

Otros frameworks de Node.js ¿No pensaríais que solo tenemos un Framework en Node.js, no? La verdad es que hay muchos y no quería desperdiciar la oportunidad de hablar de alguno de ellos. Express es el Framework que suele dar mayor control pero lo cierto es que muchos de los Frameworks en sí están basados en Express. No obstante, esto tiene un precio: Tienes que desarrollar tu propio código para ciertas cosas.

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

Derby Web³¹ - Github³² Una de las cosas en las que Derby presta más atención es en la de permitirnos usar el mismo código en el Front-end y en el Back-end. Para ello usa un motor llamado Racer que se encarga de sincronizar los datos entre navegador, servidor y base de datos. ¡Hasta tiene un resolutor de conflictos! Su mayor esfuerzo está en eliminar la tarea de tener que hacer funcionar juntos el cliente (navegador) y el servidor. Como dependencias tiene: • Express ²⁹http://meteor.com/ ³⁰https://github.com/meteor/meteor ³¹http://derbyjs.com/ ³²https://github.com/codeparty/derby

Otros frameworks de Node.js

• • • • • •

75

Socket.io Browserify Stylus Less UglifyJS MongoDB

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

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

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

Otros frameworks de Node.js

76

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

Una pequeña aclaración A lo largo del capítulo voy a intentar explicar las partes más oscuras de la documentación de Express ofreciendo ejemplos y hablando de ellos y tratar las partes más interesantes. Para todo lo demás, no está MasterCard, pero tenéis la documentación de Express (que deja mucho que desear en mi opinión) en esta dirección http://expressjs.com/api.html

Instalaci�n de Express Instalar Express es realmente sencillo y no difiere en nada de lo que hemos visto hasta ahora. No obstante, Express nos ayuda bastante si lo instalamos como paquete global y nos permite crear aplicaciones r�pidamente con algo de c�digo ya dentro. As� que, vamos a instalarla de forma global 1

npm install -g express

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

express miaplicacion

Si el directorio ya existe y no está vacío, nos avisará. Obviamente, podemos cambiar miaplicacion por lo que queramos. Esto crea la estructura básica de carpetas recomendada por Express y con los siguientes módulos: • Sistema de plantillas: Jade (lo veremos más adelante) • Sistema de CSS: CSS plano No obstante, podemos añadir más cosas añadiendo parámetros al comando: 1 2 3 4 5 6

-s -e -J -H -c stylus | less -f

Soporte para sesiones Soporte para plantillas ejs Soporte para plantillas jshtml Soporte para plantillas hogan.js Soporte para hojas de estilo con Stylus o Less Fuerza la instalación en un directorio que no esté vacío

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

Figura 4: Estructura de carpetas de Express

Creando la estructura básica

79

Express se ha encargado de crearnos su propio archivo package.json con las dependencias que necesita y unas carpetas para sugerirnos cómo organizar la aplicación. Veamos cada una de las carpetas: • public : Express diferencia el código que se muestra en el navegador del de la aplicación del servidor. Todo lo que esté en esta carpeta será accesible por el usuario que visite tu aplicación. – images, javascripts, stylesheets : Creo que os podéis hacer una idea de lo que se supone que va aquí. No obstante, me gustaría señalar que yo prefiero renombrarlas a img, js y css ya que es la convención más habitual hoy en día. • routes : Aunque entraremos en este tema en breve, cada “url” de nuestra aplicación a la que podemos acceder es una ruta y hay que definirlas en Express. Para mantenerlas separadas quedan guardadas en este directorio. • views : Por defecto Express usa Jade y las plantillas están escritas en este pseudo-lenguaje que veremos más adelante.

Welcome to Express - Bienvenido a Express Ahora hagamos un ejercicio. No abras el archivo app.js todavía. Símplemente entra en la carpeta desde el terminal y escribe lo siguiente: 1

node app.js

Rápidamente verás aparecer en pantalla: 1

Express server listening on port 3000

Abre tu navegador favorito y abre la dirección http://localhost:3000³⁷. Si todo ha ido bien, deberías poder leer el mensaje que encabeza la sección: Welcome to Express ¿Has visto qué fácil? Ya tenemos un servidor de Express corriendo en el puerto 3000 y que nos da la bienvenida. Ahora sí, te dejo abrir el código de app.js y ver lo que tiene dentro. Pero no hace falta que te vayas y dejes de leer… 1 2 3 4 5

var , , , ,

express = require('express') routes = require('./routes') user = require('./routes/user') http = require('http') path = require('path');

6 7

var app = express();

8 9 10 11 12 13 14 15

// all environments app.set('port', process.env.PORT || 3000); app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); ³⁷http://localhost:3000

Welcome to Express - Bienvenido a Express 16 17 18

81

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

19 20 21 22 23

// development only if ('development' == app.get('env')) { app.use(express.errorHandler()); }

24 25 26

app.get('/', routes.index); app.get('/users', user.list);

27 28 29 30

http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); });

Como siempre, vayamos viendo por partes. Ten en cuenta que vamos a ver todos estos temas que aquí mencionamos por encima con más detalle a lo largo del capítulo así que no te preocupes demasiado si no entiendes todo lo que lees. Lo primero que hacemos es traernos todas las dependencias que vamos a usar: express, las rutas, el módulo http y el módulo path. Lo siguiente que hacemos es crear una instancia de Express que guardamos en app. A continuación, establecemos ciertos parámetros de la configuración de Express. En orden de aparición: 1. El puerto en el que correrá la aplicación. Si establecemos una variable de entorno PORT con algún valor lo usará, de lo contrario caerá al 3000. 2. El directorio en el que están las vistas. 3. El motor de vistas que usar, por defecto Jade. 4. Uso del Favicon 5. El nivel del log ha de ser el de desarrollo. 6. Middleware que se encarga de parsear el cuerpo de la petición dando soporte para JSON, urlencode y multi-parte (para ficheros). 7. methodOverride nos permite simular las peticiones DELETE y PUT. 8. Indicamos que vamos a usar las rutas de la aplicación. 9. Indicamos que todo el contenido de public es servido. Luego, si estamos en desarrollo, añadimos el gestor de errores de Express. Le llega el turno a definir rutas, y vaya… vemos dos rutas. Nosotros la que hemos visto es la ruta /.

Welcome to Express - Bienvenido a Express

82

¿Qué pasa si entramos en /users? Pues que no responde con algo como: 1

respond with a resource

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

1

Cannot GET /prueba

Finalmente, creamos el servidor al que le pasamos nuestra aplicación para que se encargue de responder todas las peticiones. Le indicamos que escuche en el puerto que hemos configurado, y en cuanto comienza mandamos un mensaje a la consola. Como ves, son solo 30 líneas de código (que ni siquiera hemos tenido que escribir), para crear un servidor web que responde a nuestras peticiones rápidamente. Ahora vamos a seguir indagando y viendo para qué sirve cada cosa con más profundidad.

Configuración de la aplicación Cuando obtenemos la variable de la aplicación de express, a través de la función express(), podemos pasar a configurarla con profundidad con ciertos métodos que esta expone. Para todos estos ejemplos de código, supongamos que tenemos este código al principio de cada uno: 1 2

var express = require('express'); var app = express();

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

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

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

app.enable('explosion_nuclear'); // Es lo mismo que app.set('explosion_nuclear', true);

4 5 6 7

app.disable('explosion_nuclear'); // Es lo mismo que app.set('explosion_nuclear', false);

8 9 10 11

app.enabled('explosion_nuclear'); // false // Es lo mismo que app.get('explosion_nuclear') === true

12 13 14 15

app.disabled('explosion_nuclear'); // true // Es lo mismo que app.get('explosion_nuclear') === false

Configuración de la aplicación

84

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

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

Configuración de la aplicación 1 2 3 4

85

// Todos app.configure(function(){ app.set('titulo', 'Mi aplicación'); });

5 6 7 8 9

// Solo desarrollo app.configure('development', function(){ app.set('bdatos', 'localhost'); });

10 11 12 13 14

// Solo producción app.configure('production', function(){ app.set('bdatos', '0.0.0.0'); });

15 16 17 18 19

// En mi casa app.configure('casa', function(){ app.set('bdatos', 'localhost/miproyectosecreto'); });

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

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

app.locals.titulo = 'Mi aplicación'; app.locals.saludo = function(usuario) { return "¡Hola " + usuario + "!"; }

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

Configuración de la aplicación 1 2 3 4 5 6

86

app.locals.titulo = 'Mi aplicacion'; app.locals({ saludar : function(usuario) { return "¡Hola " + usuario + "!"; } });

7 8 9

console.log(app.locals.titulo); // Mi aplicacion console.log(app.locals.saludar('Manolo')); // ¡Hola Manolo!

Este tema quizá no tenga mucho sentido ahora mismo porque aun no hemos tratado las plantillas pero volveremos sobre este tema más adelante.

Rutas Ahh… las rutas… Las rutas son el auténtico corazón de Express. Estoy seguro de que no tardarás mucho en comprenderlas ya que son bastante naturales de leer y entender.

Figura 6

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

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

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

Rutas 1 2 3 4 5 6 7 8 9

88

function func1 () { console.log('1'); } function func2 () { console.log('2'); } function func3 () { console.log('3'); }

10 11 12 13 14 15

app.get('usuario',func1,func2,func3); // Equivalente a app.get('usuario', [func1,func2,func3]); // O solo usamos una app.get('usuario',func1);

Las funciones solo pueden recibir 3 parámetros y sirven para controlar lo que ocurre. Podemos decidir enviar una respuesta al usuario, enviar un error, pasar el código a la siguiente función de la lista o… no hacer nada. Los tres parámetros son los siguientes: • req : Del inglés request, que es la petición. En ella encontraremos datos de la petición tales como las cabeceras que envía el navegador o los parámetros de la ruta si es que los hubiera. • res : Del inglés response, que es la respuesta. Si no mandamos ninguna respuesta a la petición, el navegador fallará porque lleva demasiado tiempo esperando respuesta o bien con un error 324 ERR_EMPTY_RESPONSE. • next : Del inglés… ¡ohh wait! Viene a ser la siguiente función a la que llamar. Cuando nuestra ruta termine su trabajo le puede pasar el testigo a la siguiente. Si pasamos un parámetro a la llamada a next() significa que estaremos pasando un error. Recuerda que puedes llamarlos como quieras pero es la forma habitual de llamarlos y así aparece en todos los ejemplos de Express. Estos parámetros los veremos con mayor profundidad en un momento. Veamos un sencillo ejemplo de ruta para ponernos en faena. Antes de seguir, me gustaría indicar que este es el código que precede a cada ejemplo para evitar código innecesario:

89

Rutas

Ejemplo base de rutas 1 2 3 4 5

var , , , ,

express = require('express') routes = require('./routes') user = require('./routes/user') http = require('http') path = require('path');

6 7

var app = express();

8 9 10 11 12 13 14 15 16 17 18

// all environments app.set('port', process.env.PORT || 3000); app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(path.join(__dirname, 'public')));

19 20 21 22 23

// development only if ('development' == app.get('env')) { app.use(express.errorHandler()); }

24 25 26 27

http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); });

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

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

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

90

Rutas 1 2 3

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

Ahora sí… ¿ves? Puedes mandar cualquier dato al navegador como, por ejemplo, un objeto JavaScript: 1 2 3 4 5 6

app.get('/', function(req,res,next){ res.send({ 'hora' : new Date(), 'mensaje' : 'Hola mundo' }); });

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 2 3 4 5 6

app.all('/', function(req,res,next){ res.send({ 'hora' : new Date(), 'mensaje' : 'Hola mundo' }); });

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

¡Experimenta! En ocasiones es complicado probar todo tipo de peticiones de manera sencilla. No obstante, hay herramientas para ello. En Chrome podéis descargar Postman Rest Client³⁸ que nos permitirá crear cualquier tipo de petición y mandarla a cualquier punto.

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

Rutas 1 2 3

91

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

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

/mensaje/:origen/a/:destino

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

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

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

El orden de los factores altera el producto Y mucho… supongamos que tenemos la ruta de antes. Y ahora ponemos justo debajo esta: 1 2 3

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

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

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

Rutas 1 2 3 4 5

92

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

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

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

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

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

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

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

Rutas 1 2 3

93

/usuario/* // Responde a /usuario/hola/que/tal/123

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

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

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

// // // //

/libros/:categoria/:autor /libros/:categoria/:autor/limit/:limit /libros/:categoria/:autor/skip/:skip /libros/:categoria/:autor/ordenar/:ordenarpor

Y la cosa comienza a complicarse. La gente comienza a olvidar los parámetros de búsqueda. Imaginemos que queremos ofrecer libros por categoría. Podríamos usar esta URL: /libros?categoria=terror Y quizá queramos añadir el autor también: /libros?categoria=terror&autor=stephen+king ¿Cómo accedemos a esos valores? Pues quedan guardados en el objeto query: 1 2 3 4 5

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

Incluso, podemos “agruparlos”:

Rutas 1 2 3 4 5 6

// /libros?categoria=terror&autor=stephen+king&libro[tipo]=bolsillo&libro[p\ recio]= -1) { res.end('Navegador no compatible'); } else { next(); } } };

app.js 1 2 3 4 5 6

var , , , , ,

express = require('express') routes = require('./routes') user = require('./routes/user') http = require('http') path = require('path') banNavegador = require('./helpers/ban_navegador');

7 8

var app = express();

9 10 11 12 13 14 15 16 17 18 19 20 21

// all environments app.set('port', process.env.PORT || 3000); app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(banNavegador()); app.use(app.router); app.use(express.static(path.join(__dirname, 'public')));

96

Middlewares 22 23 24 25

97

// development only if ('development' == app.get('env')) { app.use(express.errorHandler()); }

26 27 28

app.get('/', routes.index); app.get('/users', user.list);

29 30 31 32

http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); });

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

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

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

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

app.get('/admin/*', adminLogado); app.get('/admin/', admin.index); app.get('/admin/usuarios', adminLogado, admin.usuarios); app.get('/admin/libros', adminLogado, admin.libros);

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

Middlewares

98

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

app.param('id_usuario', function(req, res, next, id){ Usuario.find(id, function(err, usuario){ if (err) { next(err); } else if (user) { req.params.usuario = usuario; next(); } else { next(new Error('Error al cargar el usuario')); } }); });

En este ejemplo estamos asumiendo que Usuario forma parte de algún ORM que se encargue de hacer las operaciones de lectura, actualización y borrado de una base de datos. Al colocar este código, le estamos diciendo a Express: Cada vez que en una ruta esté id_usuario, tráetelo de la base de datos y me lo dejas en la petición. ¿No es conveniente?

En resumen Vamos a intentar resumir cuándo es bueno usar cada método: • app.use : Usaremos este método cuando queramos que todas las rutas sean filtradas por este Middleware. • En línea : Usaremos este método cuando queramos que ciertas rutas sean filtradas. • Mapeado : Usaremos este método cuando queramos que al usar un parámetro específico en la ruta, éste sea tratado por la aplicación y asociado así a la petición. Por último, recalcar nuevamente el orden de los Middlewares. Éstos se ejecutan en el mismo orden en que hayan sido definidos por lo que es fundamental tener esto en cuenta a la hora de asignarlos.

Middlewares

99

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

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

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

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

app.use(express.basicAuth(function(usuario, password, callback){ Usuario.login({ usuario: usuario, password: password }, callback); }))

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

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

app.use(express.bodyParser());

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

Middlewares 1 2 3 4

100

app.use(express.logger()); app.use(express.compress()); app.use(express.methodOverride()); app.use(express.bodyParser());

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

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

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

app.use(express.session());

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

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

1

app.use(express.cookieSession());

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

Middlewares

101

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

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

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

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

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

Y tenemos la carpeta /public/imagenes/portal, podremos ver sin problemas la imagen /public/imagenes/portal/ pero si accedemos directamente al directorio, obtendremos un error. Para subsanarlo, tenemos que colocar este Middleware antes del estático: 1 2

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

Podemos pasarle algunas opciones al Middleware: • hidden : Indica si muestra o no los archivos ocultos del directorio. Por defecto es false. • icons : Indica si muestra o no los iconos del directorio. Por defecto es false. • filter : Aplica el filtro definido en este parámetro (que ha de ser una función) a los archivos. Por defecto es false.

La petición - request La gran mayoría de las cosas interesantes sobre la petición, ya las hemos visto cuando hemos hablado de las rutas. No obstante, voy a detenerme en alguno de los métodos que ya hemos visto y en otros que considero importante detenerme. No vamos a tratar todos las funciones del objeto request ya que para eso tenemos el API⁴⁰.

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

Usuario Contraseña

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

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

req.param(parametro) Express nos ofrece un método para acceder a cualquier parámetro. Hasta ahora, hemos visto que tenemos 3 tipos de parámetros (lamento desilusionarte, no hay más): • req.params : Los que van dentro de la definición de la ruta (por ejemplo: /usuarios/:id) • req.query : Los que van dentro de la cadena de consulta de la URL (por ejemplo /usuarios?id=1234) • req.body : Los que van dentro de la propia petición ⁴⁰http://expressjs.com/api.html#request

La petición - request

103

Con req.param('usuario'), Express se encarga de buscar su valor en los tres sitios por este orden: 1. req.params 2. req.body 3. req.query No obstante, no es del todo recomendable el uso de esta función a no ser que sea totalmente claro ya que puede prestar a confusión.

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

req.is('json'); // true req.is('application/*'); // true req.is('application/json'); // true req.is('html'); // false

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

req.xhr Esta propiedad es bastante útil ya que nos permite saber si es una petición del tipo AJAX. Básicamente, cuando usamos alguna función AJAX con jQuery por ejemplo, se envía la cabecera X-Requested-With con el valor XMLHttpRequest. Si revisamos el valor de req.xhr será true si es una petición de este tipo. Esto te puede ser de utilidad para capar ciertas rutas que sepas que solo vas a usar con AJAX para que no sean accesibles desde un navegador (fácilmente claro).

La respuesta - response Cuando modificamos el objeto res, estaremos modificando lo que se va a enviar de vuelta cuando se recibe una petición. Por ello, has de tener especial cuidado ya que si bien los datos de req son más de consulta, los de res se envían de vuelta al usuario (normalmente).

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

Descripción

200 301 401 404 500

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

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

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

⁴¹http://es.wikipedia.org/wiki/Anexo:C%C3%B3digos_de_estado_HTTP

La respuesta - response

105

estalogado.js 1 2 3 4 5 6 7 8

module.exports = function(req,res){ if (req.params.usuario.logado){ next(); } else { res.redirect('/login'); } };

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

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

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

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

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

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

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

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

La respuesta - response 1

106

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

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

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

app.get('/libros/:id', function(req, res){ var id = req.params.id, ruta_libro = '/libros/' + id + '.pdf';

4 5 6 7 8 9 10 11 12 13

fs.exists(ruta_libro,function(existe){ if (existe){ res.sendFile(ruta_libro); } else { res.send(404,'Libro no encontrado'); } }); });

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

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

La respuesta - response 1 2 3

107

app.get('/libros/:id', function(req, res){ var id = req.params.id, ruta_libro = '/libros/' + id + '.pdf';

4 5 6 7 8 9 10 11 12 13

fs.exists(ruta_libro,function(existe){ if (existe){ res.download(ruta_libro,'libro.pdf'); } else { res.send(404,'Libro no encontrado'); } }); });

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

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

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

La función recibe el nombre de la vista a mostrar, un objeto con parámetros para la vista, y una función de callback siendo los dos últimos parámetros opcionales. Ahora que ya hemos llegado al “final” sobre la respuesta… ¿qué te parece si nos metemos de lleno con las plantillas?

Plantillas con Jade Creo que lo he comentado el algún momento pero… Jade es el sistema de plantillas por defecto de Express. Seguramente te estés preguntando qué es una plantilla y para qué sirven. ¡Vamos a verlo! Las plantillas te permiten escribir el código mínimo necesario para que una página muestre HTML. Los motores de plantillas nos permiten hacer cosas chulas como anidar plantillas, ejecutar código en ellas, etc. Para empezar con Jade, antes qtengo que hacer una advertencia. Jade permite la indentación con tabs o con espacios pero, uses la que uses, no puedes mezclarla con la otra. Además, las plantillas de Jade, tienen extensión .jade. Lo primero que vamos a hacer es echarle un vistazo al archivo layout.jade que Express creó por nosotros cuando creamos nuestra aplicación de prueba:

layout.jade 1 2 3 4 5 6 7

doctype 5 html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body block content

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

a la hora de renderizar una ruta. Recordemos: 1 2 3

exports.index = function(req, res){ res.render('index', {title : 'Express'}); };

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

Plantillas con Jade 1

109

app.locals.title = 'Express';

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

layout.jade 1

extends layout

2 3 4 5

block content h1= title p Welcome to #{title}

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

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

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

h1 Soy un gran título // Se traduce en Soy un gran título

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

Plantillas con Jade 1 2

110

homer prueba // Se traduce en Prueba

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

Añadiendo atributos a los nodos La regla dice que para añadir un atributo a un nodo, simplemente lo metemos entre paréntesis así: 1 2 3

a(href='#', title='Descarga ahora', class='enlace') Descargar // Se traduce en Descarga\ r

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

a#descarga_ya.enlace(href='#') Descargar // Se traduce en Descargar

Para poner la id, solo tenemos que precederla de la almohadilla # y la clase va precedida de un .. No, no es casualidad, es igual que ocurre en CSS. ¿Y qué pasa si quiero añadir más de una clase?

1 2 3

a#descarga_ya.enlace.gigante(href='#') Descargar // Se traduce en Descar\ gar

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

Plantillas con Jade 1 2 3

111

#mi-div // Se traduce en ~~~~~~

4 5

### Añadiendo texto

6 7 8

Para añadir texto a un bloque, solo tenemos que dejar un espacio junto al n\ ombre del bloque, y poner el texto que nos apetezca:

9 10

{lang=text}

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

p

2

| En un lugar de la Mancha, | de cuyo nombre no quiero acordarme, | no ha mucho tiempo que vivía un hidalgo | de los de lanza en astillero, adarga antigua, | rocín flaco y galgo corredor. // Se traduce en En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha much\ o tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua,\ rocín flaco y galgo corredor.

3 4 5 6 7 8 9 10

Si interponemos la barra vertical, Jade entiende que es texto que pertenece al anterior. Si la barra vertical te resulta molesta, tenemos otra opción: 1 2 3 4 5 6 7 8 9 10

p. En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor. // Se traduce en En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha much\ o tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua,\ rocín flaco y galgo corredor.

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

Plantillas con Jade

112

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

label | Jade mola? input(type="checkbox",checked=true) // Se traduce en Jade mola?

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

Anidando elementos Ya hemos visto la anidación básica de elementos que consguimos al tabular: 1 2 3 4 5 6 7

ul li a(href="#") Uno // Se traduce en // // Uno //

No obstante, Jade nos ofrece una pequeña ayuda para no tener que usar tantos saltos y tabulaciones: 1 2 3 4 5 6

ul li:a(href="#") Uno // Se traduce en // // Uno //

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

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

Plantillas con Jade

113

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

// nombre = 'Antonio' h1= nombre // Se traduce en Antonio

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

// nombre = 'Antonio' h1 Hola #{nombre} // Se traduce en Hola Antonio

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

// id = '1234' // nombre = 'Antonio' a(href='/usuarios/' + id)= nombre a(href='/usuarios/#{id}')= nombre // Se traduce en Antonio

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

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

Plantillas con Jade 1

114

var extend = require("xtend");

2 3 4 5 6 7 8

var usuarioDefecto = { nombre : '', apellidos : '', movil : '', correo : '' };

9 10 11 12 13 14

var usuario = { nombre : 'Antonio', apellidos : 'Laguna Matías', correo : '[email protected]' };

15 16

console.log(extend(usuarioDefecto,usuario));

17 18 19 20 21

// { nombre: 'Antonio', // apellidos: 'Laguna Matías', // movil: '', // correo: '[email protected]' }

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

if locals.movil p strong Móvil | #{movil} else p strong Móvil

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

Plantillas con Jade

115

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

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

unless usuario.edad > 18 p No puedes ver aun esta página

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

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

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

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

Plantillas con Jade 1

{ usuario : { nombre : 'Antonio', correo : '[email protected]' }, intereses : ['PHP','JavaScript','HTML5','Jade']

2 3 4 5 6 7

116

}

Veamos cómo podemos mostrar esto en Jade 1 2 3 4 5 6 7 8 9 10

1

ul each interes in intereses li=interes // Se convierte en // // PHP // JavaScript // HTML5 // Jade //

ul

2 3 4 5 6 7 8 9 10

// // // // //

each valor,propiedad in usuario li strong #{propiedad}: | #{valor} Se convierte en nombre: Antonio correo: [email protected]

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

Añade contenido a un bloque ¿Os acordáis de block? Los bloques son la forma que tenemos de reusar plantillas. Imaginemos que tienes esta extructura:

Plantillas con Jade 1 2 3 4 5 6

117

html head block head style(src='/main.css') body block contenido

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

extends layout

2 3 4

block append head style(src='/validador-molon.css')

Con append añadimos el contenido que metamos al final del bloque. Si usamos prepend por el contrario, añadiremos contenido al principio del bloque.

Páginas de error en Express Ahora que ya tenemos un poco de conocimiento sobre Jade, podemos crear unas páginas personalizadas para un error 404 o 500 (que suelen ser los más comunes). En el caso del Error 500 vemos una traza de error que puede que no nos interese que el usuario vea, y en el caso de un error 404 la cosa es aun peor porque lo que vemos es CANNOT GET /… ¿Se te ocurre alguna forma de abordar el problema? … ¿No? Está bien. ¡Con Middlewares! Lo que vamos a hacer es crear dos Middlewares que se encarguen de gestionar los errores 404 y 500. Añádelos justo después del último Middleware. Ahora veremos porqué: 1 2 3 4 5

// Error 404 app.use(function(req, res) { res.status(400); res.render('404', { title: '404 - No encontrado' }); });

6 7 8 9 10 11 12 13 14

// Error 500 app.use(function(error, req, res, next) { res.status(500); res.render('500', { title: 'Oops… ¡Algo salió mal!', error: error }); });

404.jade 1 2 3 4 5

extends layout block content h1= title p Vaya, parece que aquí no está lo que estabas buscando a(href='/') Volver a la página de inicio

Páginas de error en Express

119

500.jade 1 2 3 4

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

Las plantillas Jade como ves son realmente sencillas y no tienen mucho misterio conforme a lo que hemos visto hasta ahora. Pero, ¿cómo funcionan esos dos Middlewares? Cuando Express no encuentra a ninguna ruta a la que pasarle la petición dentro de app.route, comienza a seguir por todos los Middlewares que hayamos definido por debajo. Normalmente buscará primero si puede servir algún archivo estático con app.use(express.static …. Si tampoco lo consigue, entrarán en juego estos dos Middlewares que hemos creado. El último llega en caso de que se genere un error en una ruta ya que se pasa como parámetro. ¿Por qué ponemos como error 400? El error 400 significa que la petición ha sido errónea. Algunos navegadores muestran una página personalizada del propio navegador si el estado es 404, por lo que así nos cubrimos las espaldas. Por supuesto, se puede (y quizá debe) quitar el error de la página 500 para que el usuario no vea “las tripas” de lo que ha pasado. Pero quizá sea un buen momento para que el administrador (espero que tú, querido lector) reciba un correo indicando el error que se ha producido para evitar que se produzca nuevamente.

Gestión de Login con Passport Una de las cosas más habituales que implementamos en la mayoría de las aplicaciones, es un poco de seguridad. Y con ello me quiero referir a Login principalmente. En la mayoría de las ocasiones, tendremos partes de una aplicación que no queremos que sean accesibles a no ser que se haya iniciado sesión. Passport es un genial Middleware para Express que nos ayudará a implementar un sistema de autenticación en nuestro sistema sin muchos problemas. Passport nos ofrece diferentes estrategias para autenticar y nos ofrece multitud de ventajas que en seguida veremos. Te animo a visitar su web⁴³ para echar un vistazo a las posibilidades que ofrece. Lo primero que haremos será instalarlo con la facilidad habitual: 1

$ npm install passport

Además, para nuestro pequeño ejemplo, vamos a tener que instalar una estrategia. Las estrategias (como Passport las llama) no son otra cosa que métodos que tenemos para autenticarnos en la aplicación. Una estrategia puede ser un usuario local y otra puede ser Twitter, Facebook o Google. Para nuestro ejemplo, vamos a usar la autenticación local, esto es, sin contar con servicios de terceros. Para ello, necesitamos instalar dos cosillas: • connect-flash : Este módulo nos permite pasar mensajes de error con el inicio de la sesión. • passport-local : Este módulo es, en sí, la estrategia local que nos vamos a encargar de configurar. Añádelos a tu archivo package.json tal que así: 1 2 3 4 5 6 7

"dependencies": { "express": "3.2.5", "connect-flash" : "0.1.x", "passport" : "0.1.x", "passport-local" : "0.1.x", "jade": "*" }

Y luego escribe: ⁴³http://passportjs.org/

121

Gestión de Login con Passport 1

$ npm install

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

var { { { { ];

usuarios nombre : nombre : nombre : nombre :

= [ 'Antonio', usuario : 'alaguna', password : '12345' }, 'Test', usuario : 'test1', password : '12345' }, 'Test', usuario : 'test2', password : '12345' }, 'Test', usuario : 'test3', password : '12345' },

Veamos primero la estructura de carpetas que vamos a usar:

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

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

lib/passport/estrategia-local.js 1

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

2 3

module.exports = function(usuarios){

4 5 6 7 8

function encontrarUsuario(usuario, callback){ for (var i = 0, lon = usuarios.length; i < lon; i++){ var usuarioLocal = usuarios[i]; if (usuario === usuarioLocal.usuario){

Gestión de Login con Passport

return callback(null, usuarioLocal);

9

}

10

} return callback(null, null);

11 12 13

}

14 15 16 17 18 19 20 21

function aseguraLogin(req, res, next) { if (req.isAuthenticated()) { return next(); } req.session.rutaLogin = req.route.path; res.redirect('/login') }

22 23 24 25

function serializarUsuario(usuario, done) { done(null, usuario.usuario); }

26 27 28 29 30 31

function deserializarUsuario(campoUsuario, done) { encontrarUsuario(campoUsuario, function(err, usuario){ done(err, usuario); }); }

32 33 34 35 36 37 38 39 40 41 42 43

var estrategia = new EstrategiaLocal(function(campoUsuario, campoPassword\ , done) { encontrarUsuario(campoUsuario,function(error,usuario){ if (error) { return done(error); } if (!usuario || usuario.password !== campoPassword) { return done(null, false, { message: 'Usuario y/o contraseña incorre\ ctos'}); } return done(null, usuario); }); });

44 45 46 47 48 49 50

return { estrategia : estrategia, encontrarUsuario : encontrarUsuario, aseguraLogin : aseguraLogin, serializarUsuario : serializarUsuario, deserializarUsuario : deserializarUsuario

122

Gestión de Login con Passport 51 52

123

} };

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

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

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

Gestión de Login con Passport

124

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

/routes/index.js 1 2 3

exports.index = function(req, res){ res.render('index', { title: 'Express' }); };

4 5 6 7

exports.getLogin = function(req, res){ res.render('login', { mensaje: req.flash('error') }); };

8 9 10 11 12

exports.postLogin = function(req, res) { var rutaRedirigir = req.session.rutaLogin || '/'; res.redirect(rutaRedirigir); };

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

Gestión de Login con Passport

125

/routes/usuario.js 1 2 3

exports.lista = function(req, res, usuarios){ res.render('usuarios', { usuarios: usuarios }); };

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

app.js 1 2 3 4 5 6 7

var express = require('express'), routes = require('./routes'), user = require('./routes/usuario'), http = require('http'), flash = require('connect-flash'), path = require('path'), passport = require('passport');

8 9 10 11 12 13 14

var usuarios = [ { nombre { nombre { nombre { nombre ];

: : : :

'Antonio', usuario : 'alaguna', password : '12345' }, 'Test', usuario : 'test1', password : '12345' }, 'Test', usuario : 'test2', password : '12345' }, 'Test', usuario : 'test3', password : '12345' }

15 16

var estrategiaLocal = require('./lib/passport/estrategia-local')(usuarios);

17 18 19 20

passport.use(estrategiaLocal.estrategia); passport.serializeUser(estrategiaLocal.serializarUsuario); passport.deserializeUser(estrategiaLocal.deserializarUsuario);

21 22

var app = express();

23 24 25 26 27

// all environments app.set('port', process.env.PORT || 3000); app.set('views', __dirname + '/views'); app.set('view engine', 'jade');

Gestión de Login con Passport 28 29 30 31 32 33 34 35 36 37 38

126

app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.cookieParser()); app.use(express.session({ secret: 'el oso y la doncella' })); app.use(express.methodOverride()); app.use(flash()); app.use(passport.initialize()); app.use(passport.session()); app.use(app.router); app.use(express.static(path.join(__dirname, 'public')));

39 40 41 42 43

// development only if ('development' == app.get('env')) { app.use(express.errorHandler()); }

44 45 46 47 48 49

app.get('/', routes.index); app.get('/login', routes.getLogin); app.get('/usuarios', estrategiaLocal.aseguraLogin, function(req,res){ user.lista(req,res,usuarios); });

50 51 52

app.post('/login', passport.authenticate('local', { failureRedirect: '/logi\ n', failureFlash: true }), routes.postLogin);

53 54 55 56

http.createServer(app).listen(app.get('port'), function(){ console.log('Express escuchando por el puerto ' + app.get('port')); });

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

Gestión de Login con Passport 1 2 3

127

app.use(flash()); app.use(passport.initialize()); app.use(passport.session());

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

views/login.jade 1

extends layout

2 3 4 5

block content if locals.mensaje p.error= mensaje

6 7 8 9 10 11 12 13 14 15

form(action="/login", method="post") div label(for="usuario") Usuario input#usuario(type="text",name="username") div label(for="password") Contraseña input#password(type="password",name="password") div input(type="submit") Enviar

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

Gestión de Login con Passport 1 2 3 4 5 6

128

new EstrategiaLocal({ usernameField: 'usuario', passwordField: 'contraseña' },function(campoUsuario, campoPassword, done) { ... });

De esta forma, podemos definir los campos como queramos.

views/usuarios.jade 1

extends layout

2 3 4 5 6 7 8 9 10 11 12 13 14 15

block content table thead tr th Nombre th Usuario th Password tbody each usuario in usuarios tr td=usuario.nombre td=usuario.usuario td=usuario.password

Esta sencilla vista, itera sobre la lista de usuarios y los muestra en una tabla. ¡Viva la seguridad! Aunque el ejemplo aquí indicado no sea el escenario más real posible (dado que lo habitual es usar una base de datos), sirve como base para ilustrar un proceso de login habitual de una aplicación en Node.js con Express y Jade. Es fácil sentirse abrumado al principio pero, una vez implementado, proteger una ruta es un proceso realmente sencillo que únicamente necesita añadir un sencillo Middleware que se encarga de todo.

Gestionando la subida de ficheros En casi todo momento de la vida de un programador, llega un punto en el que uno tiene que permitir que suban ficheros a través de la aplicación. Es un momento que todos tememos. Todos sabemos traer datos de una base de datos, procesarlos, etc. ¿Pero subida de ficheros? Es algo que tratamos una vez entre cien y, como no tenemos tanta experiencia, se puede llegar a hacer un mundo. Con Node.js, al principio era un verdadero dolor de muelas. No obstante, como vamos a ver ahora mismo, con Express la cosa se ha solucionado en gran medida. No necesitamos ningún módulo extra ni nada por el estilo, Express cuando lo sacamos de su caja puede hacerse cargo de la situación él solo. Es todo un niño mayor. Vamos a empezar por la parte fácil. Lo primero que necesitamos es un formulario que nos permita poner ficheros. Debería ser sencillo… vamos a ver: 1 2 3 4

form(action="/subida", method="post", enctype="multipart/form-data") // Se traduce en //

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

Gestionando la subida de ficheros 1 2 3

130

form(action="/subida", method="post", enctype="multipart/form-data") input(type="file", name="fichero") input(type='submit', value='Subir')

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

app.use(express.bodyParser());

Esta es la definición que le dí: Este Middleware es el encargado de procesar el cuerpo de las peticiones que llegan a nuestra aplicación. Para que nos entendamos, es el encargado de que una petición POST coloque los parámetros en req.params o nos facilite los archivos de haberlos. Pues, como podrás imaginar, es el que se encarga de que podamos hacernos con el fichero. Sin él, nada sería lo mismo. ¡Así que no te olvides de él! Lo que tenemos que crear es una ruta. Recuerda que en este caso, forzosamente ha de ser del tipo POST para gestionar el envío del formulario. Lo hemos definido como method pero es que además, nos viene forzado por el enctype. Vamos a probar a ver qué recibimos: 1 2 3

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

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

Gestionando la subida de ficheros 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

131

{ fichero: { domain: null, _events: {}, _maxListeners: 10, size: 163694, path: 'C:\\Users\\ALaguna\\AppData\\Local\\Temp\\c1351ae1cbc6096a5ae3c\ d1614 2f9b83', name: 'Captura.PNG', type: 'image/png', hash: null, lastModifiedDate: Mon Jun 10 2013 21:20:46 GMT+0100 (Hora de verano GM\ T), _writeStream: { ... }, ... } }

Como ves, consiste en un objeto. Si te has fijado bien, el objeto tiene una propiedad con el nombre del campo que usamos. En nuestro caso, como somos muy originales, le dimos e nombre de fichero. Además tenemos una serie de datos interesantes: • size : Este es el tamaño en bytes del archivo. Útil si queremos realizar algún tipo de validación de tamaño. • path : Esta es la ruta temporal donde el archivo está almacenado. Como ves, tiene un nombre raro y está guardado en un directorio bastante absurdo. Ahora veremos cómo podemos cambiar eso… • name : Es el nombre original que el usuario le dio al archivo, junto a su extensión. • type : Es el tipo de archivo que hemos subido. Es otro dato interesante si queremos hacer validaciones. Imagina que estamos mostrando una galería de fotos. Solo querremos que la gente suba imágenes, ¿no? Pues este es el parámetro que nos dice qué es el archivo en sí. El resto de parámetros y funciones no me parecen del todo interesantes ni necesarios en la mayoría de los casos por lo que los obviaremos por ahora. Bueno, parece que el archivo está en esa ruta fea. Si nos da por ir a esa ruta, y renombramos el archivo con la extensión original, veras que se abre y que tiene el mismo contenido. ¿Pero no es un sitio muy feo para guardar subidas? ¿Por qué se guarda ahí? En realidad, es la ubicación temporal. Esa es la de Windows. En sistemas Unix el directorio temporal, como no podría ser de otra manera, es tmp. El caso es que podemos cambiar ese directorio temporal. Vamos a ello:

Gestionando la subida de ficheros

132

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

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

Pasando un objeto de configuración a bodyParser podemos modificar levemente su comportamiento. Otra opción que tiene es keepExtensions. Si establecemos esta propiedad a true, el Middleware se encargará de que la extensión persista, por lo que en vez de tener un archivo temporal que sea así: 1

c1351ae1cbc6096a5ae3cd16142f9b83

Tendremos otro que será así: 1

c1351ae1cbc6096a5ae3cd16142f9b83.PNG

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

app.post('/subida',function(req,res){ var destino = './public/subidas/' + req.files.fichero.name;

3 4 5 6 7 8 9 10 11 12 13

if (archivoValido(req.files.fichero)){ fs.rename(req.files.fichero.path, destino, function(err){ if (err) { throw err; } res.redirect('/subidas/'+ req.files.fichero.name); }); } else { fs.unlink(req.files.fichero.path, function() { if (err) throw err; res.send('El fichero que has enviado no cumple con mis expectativas..\

Gestionando la subida de ficheros 14

.'); });

15 16 17

133

} });

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

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

Gestionando la subida de ficheros 1 2 3

134

form(action="/subida", method="post", enctype="multipart/form-data") input(type="file", name="fichero", multiple="multiple") input(type='submit', value='Subir')

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

[

2

{ domain: null, _events: {}, _maxListeners: 10, size: 19363, path: 'C:\\xampp\\htdocs\\node-upload\\public\\temp\\a14034886d10 4d828617c508f802a.PNG', name: 'Captura2.PNG', type: 'image/png', hash: null, lastModifiedDate: Mon Jun 10 2013 22:33:07 GMT+0100 (Hora de verano GMT\ ), _writeStream: [Object] }, { domain: null, _events: {}, _maxListeners: 10, size: 205450, path: 'C:\\xampp\\htdocs\\node-upload\\public\\\temp\\4a9179a8178b f22e560c2800f0ab0.PNG', name: 'Captura.PNG', type: 'image/png', hash: null, lastModifiedDate: Mon Jun 10 2013 22:33:07 GMT+0100 (Hora de verano GMT\ ), _writeStream: [Object] } ]

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

Únicamente tenemos que modificar nuestra función para ser algo así:

Gestionando la subida de ficheros 1

135

app.post('/subida',function(req,res){

2 3 4

req.files.fichero.forEach(function(fichero){ var destino = './public/subidas/' + fichero.name;

5 6 7 8 9 10 11 12 13 14 15 16

if (archivoValido(fichero)){ fs.rename(fichero.path, destino, function(err){ if (err) { throw err; } }); } else { fs.unlink(req.files.fichero.path, function() { if (err) throw err; }); } });

17 18 19

res.send('Ficheros subidos correctamente'); });

Como ves, lo que hacemos es iterar sobre la matriz de ficheros y repetir la misma operación que hicimos antes pero esta vez para mover cada fichero. Además, dado que no tiene sentido redirigir al usuario a cada foto… ¡Le decimos que ya ha terminado todo! Ánimo, pruébalo en tu servidor y verás cómo los ficheros aparecen en la carpeta de ficheros. ¡No dirás que te ha costado trabajo ehh! Ahora ya podrás decir que sabes poner un formulario de subida de ficheros para que la gente suba cosas en tu aplicación.

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

Resumen

137

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

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

$ node app.js

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

Atención Lamentablemente este capítulo es únicamente para entornos Unix ya que Windows no tiene el entorno de “demonios” que se usa en Unix. Hay forma, no obstante, de establecer un programa Node.js como servicio de Windows aunque, por el momento, queda fuera del alcance de este apéndice.

Ejecutando Node.js en segundo plano Si eres un Linuxero bueno, seguro que se te ha ocurrido que podrías ejecutar el servidor con nohup. Literalmente, nohup significa que el programa ha de ignorar la señal HUP (cuelgue) que es básicamente cuando una ventana del terminal se cierra. Entonces escribes un sencillo 1

$ nohup node NODE_ENV=production app.js &

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

Cierras la ventana. Abres el navegador y… ¡voilá! Funciona. Todo el mundo puede volver a conectar con tu aplicación y puedes empezar a sonreír. Pero esta vez la alegría dura menos. Un usuario entra en una ruta extraña y provoca un error que no tenías controlado. El servidor se detiene y otra vez nadie puede acceder. Tu jefe comienza a dudar de ti. Necesitas poner algo que se reinicie si peta. Ya tendrás tiempo de arreglarlo.

Usando forever Forever significa literalmente en inglés siempre. Así que supongo que es un módulo que nos viene que ni pintados para este caso. ¿Verdad? Siempre significa siempre, más le vale que sea verdad. Forever es una aplicación de Node.js que es bastante sencilla de usar. Antes de meternos en faena, vamos a instalarla. Necesita que la instalemos globalmente ya que puede que necesitemos más de un proceso ejecutándose a la vez y porque, sencillamente, forever es un programa de la línea de comandos. 1

$ npm install -g forever

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

$ NODE_ENV=production forever start app.js

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

info: Forever processing file: app.js

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

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

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

Usando forever

141

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

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

$ forever list

Y forever, obedientemente, nos devolverá una lista de los procesos que hay ejecutándose: 1 2 3 4 5

info: data:

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

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

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

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

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

Usando forever 1

142

$ forever stop app.js

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

$ forever stopall

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

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

$ forever restart app.js

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

$ forever restartall

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

Ubicación de los logs La primera de la que voy a hablar es la de ver el log. Podríamos hacer algo así ciertamente: 1

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

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

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

De esta manera, nos estamos asegurando de que los mensajes normales, queden guardados en miaplicacion.log mientras que los de error, en error.miaplicacion.log de esta manera, tendremos 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 1

143

$ forever logs

Y te dirá los logs que tiene ahora mismo abiertos. Escojas el método que escojas, puedes usar tail -f para ver lo que pasa con tu aplicación, especialmente en el log de errores.

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

$ crontab -u usuario -e

Si vas a usar un usuario distinto al actual, tendrás que valerte de sudo. Una vez que estés en el editor, añade la siguiente línea: 1

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

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

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

¿Qué es Socket.io? Socket.io es, de primeras, un módulo de Node.js. Sí, de esos que podemos instalar con un npm install. El módulo está pensado para facilitar la comunicación en tiempo real. Hoy en día, los navegadores modernos tienen Websockets que permiten una comunicación constante entre cliente y servidor. De esta manera, tanto uno como el otro, pueden estar hablando contínuamente y manteniendo al usuario de la aplicación al día de lo que va ocurriendo. No obstante, hay navegadores que no soportan Websockets de forma nativa y, para ello, surgió Socket.io. El módulo se compone de dos partes: Una librería del lado del cliente que se ejecuta en el propio navegador del usuario, y una librería en el lado del servidor. Vamos a intentar explicar un poco cómo funciona esto. Cuando un usuario se conecta al servidor, se produce lo que se conoce como handshaking que, literalmente significa apretón de manos. Lo que ocurre es que tanto cliente como servidor, identifican cuál es la mejor forma de comunicarse entre sí. Si tenemos a mano los websockets, todo será mucho más rápido pero, si no, Socket.io intenta usar una de estas tecnologías: • flash socket : Es un socket basado en Adobe Flash y es el que utilizan las aplicaciones Flash para mantener una conexión abierta con el servidor. • jsonp polling y ajax long-polling : Esta tecnología se basa en el envío de una petición desde el cliente al servidor. El servidor no responde inmediatamente si no que espera hasta que haya nueva información disponible. Cuando tenemos nueva información, el servidor la envía. Si se excede el tiempo de espera o se recibe una respuesta, el cliente volverá a hacer otra petición al servidor volviendo a iniciar el ciclo.

145 • ajax polling : En vez de mantener la petición abierta, el cliente hace una petición cada 5 segundos al servidor (normalmente). El recurso de estas tecnologías es para navegadores antiguos, es decir, Internet Explorer desde antes de la versión 10. Ahora que ya sabemos lo que es, vamos a meternos un poco en faena.

La aplicación que vamos a crear Dado que Socket.io es una tecnología para implementar comunicaciones en tiempo real, el ejemplo que se viene usando siempre para estos casos, no es otro que un chat. No es que no haya otros casos de usos, es que es el más sencillo de explicar y al que más utilidad se le ve rápidamente, sin necesidad de tener otros sistemas adicionales. Hasta ahora he usado Socket.io en dos proyectos más: • Gestión de centralita Asterisk en tiempo real con duraciones de llamadas, agentes que logan y deslogan en sus teléfonos, etc. • Visualizador de logs multiservidor en el navegador. No obstante, he aquí una pequeña captura que muestra la aplicación en acción:

Nuestra aplicación del Chat

La idea es:

146 • Mostrar usuarios conectados • No permitir que un usuario tenga el mismo nombre que otro • Cuando un usuario se desconecte, debe desaparecer directamente del resto de usuarios conectados al chat. • Cuando un usuario nuevo se conecta, tras indicar su nombre de usuario, recibirá en pantalla los últimos mensajes y los usuarios conectados. • Los mensajes han de estar escapados para evitar inyecciones de código en la página. He colgado además el código en Github, por lo que podéis verlo… ¡e incluso mejorarlo! https://github.com/Belelros/socketchat-es

Instalando las dependencias Nuestras dependencias son las siguientes: 1 2 3

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

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

$ express socketchat

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

$ npm install

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

$ npm install socket.io --save

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

El lado servidor

147

app.js 1 2 3 4 5 6 7

var , , , , , ,

express = require('express') routes = require('./routes') http = require('http') path = require('path') config = require('./config') socketio = require('socket.io') chat = require('./lib/chat');

8 9 10 11 12

var app = express(), server = app.listen(config.port || 3000), io = socketio.listen(server), usuarios = [], mensajes = [];

13 14

process.setMaxListeners(0);

15 16 17 18 19 20 21 22 23 24

io.configure(function(){ io.enable('browser client minification'); io.enable('browser client etag'); io.enable('browser client gzip'); io.set('log level', 1); io.set('transports', [ 'websocket', 'flashsocket', 'htmlfile', 'xhr-polling', 'jsonp-polling' ]); });

25 26 27 28 29 30 31 32 33 34

// all environments app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(path.join(__dirname, 'public')));

35 36 37 38 39 40

// development only if ('development' == app.get('env')) { app.use(express.errorHandler()); }

148 41

app.get('/', routes.index);

42 43 44 45 46 47

io.sockets.on('connection', function(socket){ socket.on('conexion',function(datos){ chat.conexion(socket,usuarios, mensajes,datos) });

48 49 50 51 52 53

socket.on('mensaje', function(datos){ socket.get('usuario', function(err, usuario){ chat.conexion(socket, mensajes, usuario,datos) }); });

54 55 56 57 58 59 60

socket.on('disconnect', function(){ socket.get('usuario', function(err,usuario){ chat.desconexion(socket,usuarios,usuario); }) }); });

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

* `socketio` : Que no es otra cosa que… bueno. No lo explico. Que ya le hem\ os dedicado parte. * `chat` : Que contiene lógica del chat para separar.

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

149 • El nivel de log al mínimo. Si no ponemos eso, aparecerán multitud de mensajes de los sockets que se van conectando. • Por último, especificamos, en orden de preferencia, los Sockets que queremos. Están ordenados de más rápidos a más lentos. La parte intermedia del resto del archivo, es código normal que debería sonaros a estas alturas. Si no es así… ¡volved al principio! Ahora vamos a la parte que más nos interesa. Socket.io nos expone un objeto sockets que no es otra cosa que un EventEmitter.

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

Por regla general, nos interesa estar pendientes del evento connection que es cuando alguien abre un canal de comunicaciones desde un navegador, contra nuestro servidor. En ese momento, dentro del callback, recibimos el objeto socket de la persona que se acaba de conectar que a su vez es un EventEmitter también. Ahora nos quedamos escuchando a 3 eventos que puedan emitir estos sockets: • conexion : Este es el evento en el que se conecta alguien y nos da un nombre de usuario. Esto hará que aparezca en la lista de usuarios conectados y generará un usuario único en caso de que haya más de uno con el mismo nombre. • mensaje : Este es el evento que ocurre cuando un usuario envía un mensaje al chat. La función solo se ejecuta cuando obtenemos el valor del usuario del Socket, que es una operación asíncrona. En seguida veremos más sobre esto. • disconnect : Este es el evento que ocurre cuando un usuario abandona nuestra aplicación. Aquí querremos avisar a la gente de que esta persona se ha ido, manteniendo la lista de usuarios conectados actualizada. Entiendo que esto es un poco confuso ahora mismo. Piensa en toda esta última parte como en el primer día en una empresa de seguridad. Tu jefe, el primer día, te dirá algo así: • Si alguien entra en esta habitación sin autorización, pides refuerzos y lo arrestas. • Si alguien atraca a alguien, acude cuanto antes para intentar arrestarlo. • Cada día al entrar has de dejar constancia de cuando te vas y cuando entras.

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

lib/chat.js 1

var helperUsuarios = require('../helpers/usuarios')

2 3 4 5

exports.conexion = function(socket, usuarios, mensajes, datos){ var usuario = helperUsuarios.usuarioUnico(usuarios,datos.usuario), temp = [];

6 7 8 9

socket.set('usuario', usuario); temp.push(usuario); usuarios.push(usuario);

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

socket.broadcast.emit('conexion',temp); socket.broadcast.emit('mensaje',[{ 'tipo' : 'sistema', 'usuario' : null, 'mensaje' : usuario + ' se ha unido al chat' }]); socket.emit('conexion',usuarios); socket.emit('mensaje',mensajes); }; exports.mensaje = function(socket, mensajes, usuario, datos){ var mensaje = { 'tipo' : 'normal', 'usuario' : usuario, 'mensaje' : datos.mensaje };

26 27 28 29 30 31 32 33 34 35

io.sockets.emit('mensaje', [mensaje]); mensajes.push(mensaje); }; 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,

151 'mensaje' : usuario + ' ha abandonado el chat' }]);

36 37 38

}

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

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

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

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

helpers/usuarios.js 1 2

exports.usuarioUnico = function(usuarios, usuario){ var aux = usuario, numero = 1;

3 4 5 6

while (usuarios.indexOf(aux) !== -1){ aux = [usuario,numero++].join('_'); }

7 8 9

return aux; };

Como ves, no es nada raro, ni excepcional. Simplemente vamos encadenando números al final, hasta que alguno está disponible. Por ejemplo, si “Usuario” ya está ocupado, nos dará “Usuario1”, luego “Usuario2” y así sucesivamente. ¿A alguien le suena el IRC? ¿No? ¿Me estoy haciendo mayor? Sigamos… Con la función set, guardamos un valor en el socket. Dado que es un dato que vamos a usar a menudo, tiene sentido hacerlo así. Aunque es una operación asíncrona, nos da igual cuándo termine. En una matriz temporal, guardamos el usuario que acaba de conectarse. Luego entenderemos porqué. Ahora es cuando ocurre la magia. Vamos línea a línea que creo que merece la pena:

152 1

socket.broadcast.emit('conexion',temp);

Esta línea lo que hace es emitir a todos los sockets, menos al que se acaba de conectar, el valor de temp, que no es otro que una matriz que contiene el usuario que se acaba de conectar. 1 2 3 4 5

socket.broadcast.emit('mensaje',[{ 'tipo' : 'sistema', 'usuario' : null, 'mensaje' : usuario + ' se ha unido al chat' }]);

Esta línea lo que hace es emitir a todos los sockets, menos al que se acaba de conectar, un mensaje del tipo sistema, indicando que el usuario se ha conectado al chat. 1 2

socket.emit('conexion',usuarios); socket.emit('mensaje',mensajes);

Estas dos líneas lo que hacen es enviar al socket que se acaba de conectar, los usuarios actuales y los mensajes. Como ves, funciones que son bastante similares, hacen cosas totalmente distintas. Entiendo que es un poco lioso pero, a la larga, le acabas cogiendo el truco. Para que no te pierdas, aquí pongo una pequeña lista que puedes imprimir en tamaño A1 y colgarla en la pared… así no se te olvidará. • • • •

Enviar al socket actual en solitario: socket.emit('mensaje', 'prueba'); Enviar a todos los clientes, incluyendo al que emite: io.sockets.emit('mensaje','prueba'); Enviar a todos los clientes, menos al que emite: socket.broadcast.emit('mensaje','prueba'); Enviar a un socket en particular: io.sockets.socket('id_del_socket').emit('mensaje','prueba');

Entiendo que ahora mismo las cosas pueden no tener mucho sentido, pero has de tener en cuenta, que esto consta de dos partes. El servidor se encarga de enviar y luego todo empieza a cobrar sentido en el cliente, que veremos en seguida.

Mensaje La función del mensaje recibe como parámetro el socket que ha enviado el mensaje, la lista de mensajes, el usuario que ha enviado el mensaje (obtenido del socket con la función socket.get('usuario')) y los datos que no es otra cosa que un objeto que contiene un parámetro mensaje que tiene el mensaje que acaba de ser enviado. La función se encarga de enviar el mensaje a todo el mundo, añadiendo el tipo de mensaje y el usuario que lo ha enviado y lo guarda en la lista de mensajes.

153

Desconexión exports.desconexion = function(socket,usuarios,usuario){ usuarios.splice(usuarios.indexOf(usuario),1); socket.broadcast.emit(‘desconexion’,{ usuario: usuario }); socket.broadcast.emit(‘mensaje’,[{ ‘tipo’ : ‘sistema’, ‘usuario’ : null, ‘mensaje’ : usuario + ‘ ha abandonado el chat’ }]); } La desconexión es nuestra última función y recibe el socket traidor que está abandonando nuestra aplicación, la lista de usuarios actual, y el usuario traidor que ha decidido abandonar. Esta función se encarga de avisar a todo el mundo de que alguien ha abandonado el chat y de enviar un mensaje del sistema indicando que esta persona se ha marchado.

El lado del cliente Del lado del cliente solo tenemos un único archivo, que se encarga de gestionar todo. Te aconsejo que mientras lees, querido lector, saltes entre los apartados del cliente y el servidor, para ver cómo engancha todo. ¡No te pierdas! Antes de empezar con el código JavaScript, vamos a ver la plantilla de Jade, que como verás, es realmente sencilla:

views/index.jade 1

extends layout

2 3 4 5 6 7 8 9 10 11 12 13

block content #mensajes #lista-mensajes .controles textarea#mensaje input#enviar(type="button", value="Enviar") #conectados ul script(src='/socket.io/socket.io.js') script(src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js") script(src="javascripts/chat.js")

Como ves, son un par de div, una para almacenar los mensajes y otra para los usuarios. Dentro tienen algunos controles para que el chat pueda cobrar vida como un textarea en la que escribiremos los mensajes, nuestro botón de enviar, etc. Lo único que quizá te sorprenda es el archivo de socket.io. Si miras tus carpetas, verás que no tienes ninguna carpeta que se llame así. No obstante, Express y Socket.io son viejos amigos, y gracias a la

154 configuración que hemos hecho antes, Express será capaz de servir el archivo de Socket.io necesario para haya comunicación con el servidor. Ahora sí, ¿estás preparado? ¿De verdad? Pues sigue leyendo, vamos a ver nuestro archivo que contiene toda la lógica del chat.

public/javascripts/chat.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

(function($){ var usuario = prompt('Nombre de usuario','Usuario'); var Chat = { socket : null, el : { listaUsuarios : $('#conectados ul'), listaMensajes : $('#lista-mensajes'), textoMensaje : $('#mensaje'), botonEnviar : $('#enviar') }, iniciar : function(usuario){ this.conectarSocket(); // Enviando el usuario al servidor this.socket.emit('conexion', { usuario: usuario }); this.asociarEventos(); }, conectarSocket : function(){ this.socket = io.connect('http://localhost:3000'); }, asociarEventos : function(){ this.socket.on('conexion', $.proxy(this.anadirUsuarioAChat, this)); this.socket.on('desconexion', $.proxy(this.eliminarUsuarioDeChat, thi\ s)); this.socket.on('mensaje', $.proxy(this.anadirMensaje, this)); this.el.botonEnviar.on('click', $.proxy(this.enviarMensaje, this)) }, anadirUsuarioAChat : function(datos){ var html = '';

29 30 31 32

$.each(datos,function(i,usuario){ html += '' + usuario + ''; });

33 34 35

this.el.listaUsuarios.append(html); },

155 eliminarUsuarioDeChat : function(datos){ this.el.listaUsuarios.find('li').filter(function(){ return datos.usuario === $(this).text() }).remove(); }, anadirMensaje : function(mensajes){ var html = '';

36 37 38 39 40 41 42 43

$.each(mensajes, function(i, mensaje){ var clase = mensaje.tipo ? ' class="'+ mensaje.tipo +'"' : ''; html += ''; if (mensaje.usuario) { html += '' + mensaje.usuario + ': '; } html += mensaje.mensaje; });

44 45 46 47 48 49 50 51 52

this.el.listaMensajes.append(html); }, enviarMensaje : function(e){ e.preventDefault(); this.socket.emit('mensaje', { mensaje : this.escapar(this.el.textoMensaje.val()) }); this.el.textoMensaje.val(''); }, escapar : function(texto){ return String(texto) .replace(/&(?!\w+;)/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

};

70 71 72 73 74 75 76 77

if (usuario){ Chat.iniciar(usuario); } else { alert('Sin usuario no tienes acceso al chat!'); }

156 78

})(jQuery);

Vamos poco a poco… que no tenemos prisa alguna. Lo primero que hacemos es, nada más cargar la página, pedir el usuario con la función prompt de JavaScript. Por defecto, el valor será Usuario pero el usuario (valga la redundancia) es libre de cambiarlo. Saltémonos por un segundo todo lo de var Chat… y llega al final del todo. En caso de que tengamos un usuario, el Chat se inicia y, en caso contrario, damos una alerta diciendo que el chat no funciona sin usuario. Fácil, ¿no?

Más información Si te has fijado, todo el código está envuelto en una función anónima auto-invocada de JavaScript. Este es un patrón muy conocido pero, si por algún motivo, te resulta ajeno, te recomiendo que te leas un artículo que escribí⁴⁵ hace tiempo en mi blog, hablando sobre el tema.

Ahora volvamos sobre el Chat. El Chat no es otra cosa que un objeto de JavaScript con propiedades y métodos. Dentro de el, guardamos todos los elementos del DOM que hemos creado en nuestra plantilla. Esto está considerado una buena práctica ya que solo buscamos en las tripas del DOM una vez… ¡al principio! La función iniciar es la que vimos antes en nuestro if abajo del todo. Esta es la que se encarga de que todo empiece a funcionar. Los nombres de las funciones son bastante aclaratorios, pero vamos a verlos. Lo primero que hacemos es conectar al socket. Básicamente es esta sentencia: 1

this.socket = io.connect('http://localhost:3000');

En este momento es en el que, en el servidor, se ejecuta la función que le pasamos al método on('connect') y, a partir de ahí, el servidor está preparado para escuchar los mensajes que vengan del socket. Lo siguiente que hacemos es emitir un evento conexion con el usuario que nuestro visitante ha elegido. En este punto, el servidor intentará decidir si puede quedarse con él o necesita generarle alguno nuevo. La siguiente función es asociarEventos que… no te lo vas a creer… ¡asocia eventos a funciones! Solo nos interesan 4 cosas: ⁴⁵http://www.funcion13.com/2012/10/21/funcion-anonima-auto-invocada-en-javascript/

157 • • • •

conexion : Este evento es el que ocurre desde el servidor cuando alguien se conecta. desconexion : Este evento es el que ocurre desde el servidor cuando alguien se desconecta. mensaje : Este evento es el que ocurre desde el servidor cuando alguien envía un mensaje. click : Cuando alguien hace click en el botón de enviar, significa que nuestra aplicación tiene que enviar el mensaje que hay en textoMensaje al servidor y de ahí, se propagará al resto.

Todas estas funciones usan la función de jQuery $.proxy que no es otra cosa que una función que toma una como parámetro, y devuelve otra que se ejecutará en el contexto especificado. Eso queda muy bonito pero, ¿qué quiere decir? Bueno, si habéis jugado con JavaScript, sabréis que el elemento this tiende a perderse cada poco. Por ejemplo, en la función del botón de click, si no usáramos $.proxy, el valor de this sería el del botón. Eso resulta realmente molesto así que de esta forma lo solucionamos de manera que this siempre será nuestro objeto Chat.

Más información Una vez más, este es uno de los temas peliagudos de JavaScript que necesitan un momento de comprensión. Hace tiempo escribí un artículo que escribí⁴⁶ en el blog que trataba este y otros temas de JavaScript. ¡Te recomiendo una lectura!

Ahora vamos a ver las funciones que son las gestoras de los eventos a los que estamos escuchando. anadirUsuariosAChat recibe una matriz. Es por eso que en el servidor añadíamos un único usuario

a una matriz temporalmente ya que de esta forma, podemos usar esta función para añadir tantos usuarios como queramos. Gracias a la función each de jQuery, vamos iterando por la matriz para crear nuestra cadena HTML que añadiremos a la lista de usuarios. La función opuesta, eliminarUsuarioDeChat, se encarga de recorrer la lista de usuarios conectados y filtrarlos. Si el usuario coincide con el que está dentro de datos.usuario, eliminará el nodo de la lista y desaparecerá. anadirMensaje recibe al igual que la de los usuarios, una matriz de mensajes para poder reutilizarla. El funcionamiento es similar al de los usuarios pero es un poco diferente ya que creamos un párrafo

y le añadimos una clase en función del tipo. Si viene el usuario, pues lo añadimos al principio del mensaje para que sepamos quién lo ha escrito. Puede que no lo recuerdes, pero los mensajes del tipo sistema, no tienen usuario alguno ya que son como notificaciones del chat al resto de la gente. Por ello cuentan con un estilo especial. ⁴⁶http://www.funcion13.com/2012/03/16/comprendiendo-las-variables-objetos-funciones-alcance-y-prototype-en-javascript/

158 La función enviarMensaje es la que se encarga de gestionar el click sobre el botón enviar, lo primero que hacemos es prevenir el comportamiento por defecto del botón con e.preventDefault para evitar que intente enviar un formulario que no exista. Gracias a this.socket.emit emitimos un mensaje al que le indicamos únicamente el parámetro mensaje después de haberlo escapado. Y como ves, eso es todo. Gracias a la separación de conceptos, minimizamos enormemente la cantidad de funciones y código que tenemos que escribir, tanto anadirMensaje como anadirUsuariosAChat son funciones realmente genéricas y que se usan tanto en la conexión inicial para añadir todos los mensajes y usuarios, como cuando ocurre de forma individual. Ahora te toca a ti, destripar la aplicación, instalarla ¡e intentar mejorarla!

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF