Você está na página 1de 40

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

El Libro para Principiantes


en Node.js
Un tutorial de Node.js por: Manuel Kiessling & Herman A.
Junge

Sobre el Tutorial
El objetivo de este documento es ayudarte a empezar con el desarrollo de
aplicaciones para Node.js, ensendote todo lo que necesites saber acerca de
JavaScript "avanzado" sobre la marcha. Este tutorial va mucho ms all del tpico
manual "Hola Mundo".

Status
Ests leyendo la versin final de este libro, es decir, las actualizaciones solo sern
hechas para corregir errores o para reflejar cambiar en nuevas versiones de
Node.js.
Las muestras de cdigo de este libro estn probadas para funcionar con la versin
0.6.11 de Node.js.

Audiencia Objetivo
Este documento probablemente ser mejor entendido por los lectores que tengan
un trasfondo similar al mo: Programadores experimentados en al menos un
lenguaje orientado al objeto, como Ruby, Python, PHP o Java; poca experiencia
con JavaScript, y ninguna experiencia en Node.js.
El que este documento est orientado a desarrolladores que ya tienen experiencia
con otros lenguajes de programacin significa que no vamos a cubrir temas
realmente bsicos como tipos de datos, variables, estructuras de control y
similares. Debes saber acerca de estos tpicos para entender este documento.
Sin embargo, dado que las funciones y objetos en JavaScript son diferentes de sus

1 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

contrapartes en la mayora de los lenguajes, estos sern explicados con ms


detalle.

Estructura de este documento


Al Trmino de este documento, habrs creado una aplicacin Web completa, que
permita a los usuarios de sta el ver pginas web y subir archivos.
La cual, por supuesto, no va ser nada como la "aplicacin que va a cambiar el
mundo", no obstante eso, nosotros haremos la milla extra y no vamos slo a
codificar una aplicacin lo "suficientemente simple" para hacer estos casos de uso
posible, sino que crearemos un framework sencillo, pero completo, a fin de poder
separar los distintos aspectos de nuestra aplicacin. Vers lo que esto significa en
poco tiempo.
Empezaremos por mirar cmo el desarrollo en JavaScript en Node.js es diferente
del desarrollo en JavaScript en un browser.
Luego, nos mantendremos con la vieja tradicin de escribir una aplicacin "Hola
Mundo", la cual es la aplicacin ms bsica de Node.js que "hace" algo.
Enseguida, discutiremos que tipo de "aplicacin del mundo real" queremos
construir, disectaremos las diferentes partes que necesitan ser implementadas
para ensamblar esta aplicacin, y empezaremos trabajando en cada una de estas
partes paso a paso.
Tal y cual lo prometido, aprenderemos sobre la marcha acerca de algunos de los
muchos conceptos avanzados de JavaScript, como hacer uso de ellos, y ver el
porqu tiene sentido el hacer uso de estos conceptos en vez de los que ya
conocemos por otros lenguajes de programacin.

Tabla de Contenidos

JavaScript y Node.js

2 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

JavaScript y T
Antes que hablemos de toda la parte tcnica, tommonos un minuto y hablemos
acerca de ti y tu relacin con JavaScript. Este captulo est aqu para permitirte
estimar si tiene sentido el que sigas o no leyendo este documento.
Si eres como yo, empezaste con el "desarrollo" HTML hace bastante tiempo,
escribiendo documentos HTML. Te encontraste en el camino con esta cosa
simptica llamada JavaScript, pero solo la usabas en una forma muy bsica,
agregando interactividad a tus pginas de cuando en cuando.
Lo que realmente quisiste era "la cosa real", Queras saber cmo construir sitios
web complejos - Aprendiste un lenguaje de programacin como PHP, Ruby, Java,
y empezaste a escribir cdigo "backend".
No obstante, mantuviste un ojo en JavaScript, y te diste cuenta que con la
introduccin de jQuery, Prototype y otros, las cosas se fueron poniendo ms
avanzadas en las Tierras de JavaScript, y que este lenguaje era realmente ms que
hacer un window.open().
Sin embargo, esto era todo cosa del frontend ,y aunque era agradable contar con
jQuery a tu disposicin en cualquier momento que te sintieras con nimo de
sazonar una pgina web, al final del da, lo que eras a lo ms, era un usuario de
JavaScript, pero no, un desarrollador de JavaScript.
Y entonces lleg Node.js. JavaScript en el servidor, Qu hay con eso?
Decidiste que era ya tiempo de revisar el nuevo JavaScript. Pero espera: Escribir
aplicaciones Node.js es una cosa ; Entender el porqu ellas necesitan ser escritas
en la manera que lo son significa entender JavaScript! Y esta vez es en serio.
Y aqu est el problema: Ya que JavaScript realmente vive dos, o tal vez tres vidas
(El pequeo ayudante DHTML de mediados de los 90's, las cosas ms serias tales
como jQuery y similares, y ahora, el lado del servidor), no es tan fcil encontrar
informacin que te ayude a aprender JavaScript de la "manera correcta", de forma
de poder escribir aplicaciones de Node.js en una apariencia que te haga sentir que
no slo ests usando JavaScript, sino que tambin estn desarrollando con l.

3 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Porque ah est el asunto: Ya eres un desarrollador experimentado, y no quieres


aprender una nueva tcnica simplemente metiendo cdigo aqu y all
mal-aprovechndolo; Quieres estar seguro que te ests enfocando en un ngulo
correcto.
Hay, por supuesto, excelente documentacin afuera. Pero la documentacin por s
sola no es suficiente. Lo que se necesita es una gua.
Mi objetivo es proveerte esta gua.

Una Advertencia
Hay algunas personas realmente excelente en JavaScript. No soy una de ellas.
Yo soy realmente el tipo del que te he hablado en los prrafos previos. S un par
de cosas acerca de desarrollar aplicaciones backend, pero an soy nuevo al
JavaScript "real" y an ms nuevo a Node.js. He aprendido solo recientemente
alguno de los aspectos avanzados de JavaScript. No soy experimentado.
Por lo que este no es un libro "desde novicio hasta experto". Este es ms bien un
libro "desde novicio a novicio avanzado".
Si no fallo, entonces este ser el tipo de documento que deseo hubiese tenido
cuando empec con Node.js.

JavaScript del Lado del Servidor


Las primeras encarnaciones de JavaScript vivan en los browsers. Pero esto es slo
el contexto. Define lo que puedes hacer con el lenguaje, pero no dice mucho acerca
de lo que el lenguaje mismo puede hacer. JavaScript es un lenguaje "completo":
Lo puedes usar en muchos contextos y alcanzar con ste, todo lo que puedes
alcanzar con cualquier otro lenguaje "completo".
Node.js realmente es slo otro contexto: te permite correr cdigo JavaScript en el
backend, fuera del browser.
Para ejecutar el cdigo JavaScript que tu pretendes correr en el backend, este
necesita ser interpretado y, bueno, ejecutado, Esto es lo que Node.js realiza,

4 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

haciendo uso de la Maquina Virtual V8 de Google, el mismo entorno de ejecucin


para JavaScript que Google Chrome utiliza.
Adems, Node.js viene con muchos mdulos tiles, de manera que no tienes que
escribir todo de cero, como por ejemplo, algo que ponga un string a la consola.
Entonces, Node.js es en realidad dos cosas: un entorno de ejecucin y una librera.
Para hacer uso de stas (la librera y el entorno), Necesitas instalar Node.js. En
lugar de repetir el proceso aqu. Te ruego visitar las instrucciones oficiales de
instalacin, Por Favor vuelve una vez que ests arriba y corriendo tu versin de
Node.js

"Hola Mundo"
Ok. Saltemos entonces al agua fra y escribamos nuestra primera aplicacin
Node.js: "Hola Mundo".
Abre tu editor favorito y crea un archivo llamado holamundo.js. Nosotros
queremos escribir "Hola Mundo" a STDOUT, y aqu est el cdigo necesario para
hacer esto:

console.log("Hola Mundo");

Graba el archivo, y ejectalo a travs de Node.js:


node holamundo.js

Este debera retornar Hola Mundo en tu monitor.


Ok, esto es aburrido, de acuerdo? As que escribamos alguna cosa real.

Una Aplicacin Web Completa con Node.js


5 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Los casos de Uso


Mantengmoslo simple, pero realista:
El Usuario debera ser capaz de ocupar nuestra aplicacin con un browser.
El Usuario debera ver una pgina de bienvenida cuando solicita
http://dominio/inicio, la cual despliega un formulario de sbida.
Eligiendo un archivo de imagen para subir y enviando el formulario, la
imagen debera ser subida a http://dominio/subir, donde es desplegada una
vez que la sbida este finalizada.

Muy bien. Ahora, tu puedes ser capaz de alcanzar este objetivo googleando y
programando lo que sea, pero eso no es lo que queremos hacer aqu.
Ms que eso, no queremos escribir simplemente el cdigo ms bsico posible para
alcanzar este objetivo, no importa lo elegante y correcto que pueda ser este cdigo.
Nosotros agregaremos intencionalmente ms abstraccin de la necesaria de
manera de poder tener una idea de lo que es construir aplicaciones ms complejas
de Node.js.

La Pila de Aplicaciones
Hagamos un desglose a nuestra aplicacin. Qu partes necesitan ser
implementadas para poder satisfacer nuestros casos de uso?
Queremos servir pginas web, de manera que necesitamos un Servidor
HTTP.
Nuestro servidor necesitar responder directamente peticiones (requests),
dependiendo de qu URL sea pedida en este requerimiento, es que
necesitaremos algn tipo de enrutador (router) de manera de mapear los
peticiones a los handlers (manejadores) de stos.
Para satisfacer a los peticiones que llegaron al servidor y han sido ruteados
usando el enrutador, necesitaremos de hecho handlers (manejadores)
de peticiones
El Enrutador probablemente debera tratar cualquier informacin POST que
llegue y drsela a los handlers de peticiones en una forma conveniente, luego
necesitaremos manipulacin de data de peticin

6 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Nosotros no solo queremos manejar peticiones de URLs, sino que tambin


queremos desplegar contenido cuando estas URLs sean pedidas, lo que
significa que necesitamos algn tipo de lgica en las vistas a ser utilizada
por los handlers de peticiones, de manera de poder enviar contenido al
browser del Usuario.
Por ltimo, pero no menos importante, el Usuario ser capaz de subir
imgenes, as que necesitaremos algn tipo de manipulacin de subidas
quien se har cargo de los detalles.

Pensemos un momento acerca de como construiramos esta pila de aplicaciones


con PHP. No es exactamente un secreto que la configuracin tpica sera un
Apache HTTP server con mod_php5 instalado.
Lo que, a su vez, significa que el tema "Necesitamos ser capaces de servir pginas
web y recibir peticiones HTTP" ni siquiera sucede dentro de PHP mismo.
Bueno, con Node.js, las cosas son un poco distintas. Porque con Node.js, no solo
implementamos nuestra aplicacin, nosotros tambin implementamos todo el
servidor HTTP completo. De hecho, nuestra aplicacin web y su servidor web son
bsicamente lo mismo.
Esto puede sonar como mucho trabajo, pero veremos en un momento que con
Node.js, no lo es.
Empecemos por el principio e implementemos la primera parte de nuestra pila, el
servidor HTTP..

Construyendo la Pila de Aplicaciones


Un Servidor HTTP Bsico
Cuando llegu al punto donde quera empezar con mi primera aplicacin Node.js
"real", me pregunt no solo como la iba a programar, sino que tambin, como
organizar mi cdigo.
Necesitar tenerlo todo en un archivo? Muchos tutoriales en la Web que te
ensean cmo escribir un servidor HTTP bsico en Node.js tienen toda la lgica
en un solo lugar. Qu pasa si yo quiero asegurarme que mi cdigo se mantenga
leble a medida que le vaya agregando ms cosas?

7 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Resulta, que es relativamente fcil de mantener los distintos aspectos de tu cdigo


separados, ponindolos en mdulos.
Esto te permite tener un archivo main limpio, en el cual ejecutas Node.js, y
mdulos limpios que pueden ser utilizados por el archivo main entre muchos
otros.
As que vamos a crear un archivo main el cual usaremos para iniciar nuestra
aplicacin, y un archivo de mdulo dnde residir el cdigo de nuestro servidor
HTTP.
Mi impresin es que es ms o menos un estndar nombrar a tu archivo principal
como index.js. Tiene sentido tambin que pongamos nuestro mdulo de servidor
en un archivo llamado server.js.
Empecemos con el mdulo del servidor. Crea el archivo server.js en el directorio
raz de tu proyecto, y llnalo con el cdigo siguiente:

var http = require("http");


http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}).listen(8888);

Eso es! Acabas de escribir un servidor HTTP activo. Probmoslo ejecutndolo y


testendolo. Primero ejecuta tu script con Node.js:
node server.js

Ahora, abre tu browser y apntalo a http://localhost:8888/. Esto debera


desplegar una pgina web que diga "Hola Mundo".
Interesante, no? Qu tal si hablamos de que est pasando aqu y dejamos la
pregunta de 'cmo organizar nuestro proyecto' para despus? Prometo que
volveremos a esto.

8 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Analizando nuestro servidor HTTP


Bueno, entonces, analicemos que est pasando aqu.
La primera lnea require, requiere al mdulo http que viene incluido con Node.js y
lo hace accesible a travs de la variable http.
Luego llamamos a una de las funciones que el mdulo http ofrece: createServer.
Esta funcin retorna un objeto, y este objeto tiene un mtodo llamado listen
(escucha), y toma un valor numrico que indica el nmero de puerto en que
nuestro servidor HTTP va a escuchar.
Por favor ignora por un segundo a la definicin de funcin que sigue a la llave de
apertura de http.createServer.
Nosotros podramos haber escrito el cdigo que inicia a nuestro servidor y lo hace
escuchar al puerto 8888 de la siguiente manera:
var http = require("http");
var server = http.createServer();
server.listen(8888);

Esto hubiese iniciado al servidor HTTP en el puerto 8888 y no hubiese hecho


nada ms (ni siquiera respondido alguna peticin entrante).
La parte realmente interesante (y rara, si tu trasfondo es en un lenguaje ms
conservador, como PHP) es que la definicin de funcin est ah mismo donde
uno esperara el primer parmetro de la llamada a createServer().
Resulta que, este definicin de funcin ES el primer (y nico) parmetro que le
vamos a dar a la llamada a createServer(). Ya que en JavaScript, las funciones
pueden ser pasadas de un lado a otro como cualquier otro valor.

Pasando Funciones de un Lado a Otro


Puedes, por ejemplo, hacer algo como esto:

9 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

function decir(palabra) {
console.log(palabra);
}
function ejecutar(algunaFuncion, valor) {
algunaFuncion(valor);
}
ejecutar(decir, "Hola");

Lee esto cuidadosamente! Lo que estamos haciendo aqu es, nosotros pasamos la
funcin decir() como el primer parmetro de la funcin ejecutar. No el valor de
retorno de decir, sino que decir() misma!
Entonces, decir se convierte en la variable local algunaFuncion dentro de
ejecutar, y ejecutar puede llamar a la funcin en esta variable usando
algunaFuncion() (agregando llaves).
Por supuesto, dado que decir toma un parmetro, ejecutar puede pasar tal
parmetro cuando llama a algunaFuncion.
Nosotros podemos, tal como lo hicimos, pasar una funcin por su nombre como
parmetro a otra funcin. Pero no estamos obligados a tener que definir la funcin
primero y luego pasarla. Podemos tambin definir y pasar la funcin como un
parmetro a otra funcin todo al mismo tiempo:

function ejecutar(algunaFuncion, valor) {


algunaFuncion(valor);
}
ejecutar(function(palabra){ console.log(palabra) }, "Hola");

(N.del T.: function es una palabra clave de JavaScript).


Nosotros definimos la funcin que queremos pasar a ejecutar justo ah en el lugar
donde ejecutar espera su primer parmetro.
De esta manera, no necesitamos darle a la funcin un nombre, por lo que esta
funcin es llamada funcin annima.

10 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Esta es una primera ojeada a lo que me gusta llamar JavaScript "avanzado". Pero
tommoslo paso a paso. Por ahora, aceptemos que en JavaScript, nosotros
podemos pasar una funcin como un parmetro cuando llamamos a otra funcin.
Podemos hacer esto asignando nuestra funcin a una variable, la cual luego
pasamos, o definiendo la funcin a pasar en el mismo lugar.

De Qu manera el pasar funciones hace que


nuestro servidor HTTP funcione
Con este conocimiento, Volvamos a nuestro servidor HTTP minimalista:

var http = require("http");


http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}).listen(8888);

A estas alturas, debera quedar claro lo que estamos haciendo ac: Estamos
pasndole a la funcin createServer una funcin annima.
Podemos llegar a lo mismo refactorizando nuestro cdigo as:
var http = require("http");
function onRequest(request, response) {
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}
http.createServer(onRequest).listen(8888);

Quizs ahora es un buen momento para preguntar: Por Qu estamos haciendo


esto de esta manera?

Callbacks Manejadas por Eventos


La respuesta a) No es una no muy fcil de dar (al menos para m), y b) Yace en la

11 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

naturaleza misma de como Node.js trabaja: Est orientado al evento, esa es la


razn de por qu es tan rpido.
Podras tomarte un tiempo para leer este excelente post (en ingls) de Felix
Geisendrdfer: Understanding node.js para alguna explicacin de trasfondo.
Al final todo se reduce al hecho que Node.js trabaja orientado al evento. Ah, y s,
Yo tampoco s exactamente qu significa eso. Pero voy a hacer un intento de
explicar, el porqu esto tiene sentido para nosotros, que queremos escribir
aplicaciones web en Node.js.
Cuando nosotros llamamos al mtodo http.createServer, por supuesto que no slo
queremos que el servidor se quede escuchando en algn puerto, sino que tambin
queremos hacer algo cuando hay una peticin HTTP a este servidor.
El problema es, que esto sucede de manera asincrnica: Puede suceder en
cualquier momento, pero solo tenemos un nico proceso en el cual nuestro
servidor corre.
Cuando escribimos aplicaciones PHP, esto no nos molesta en absoluto: cada vez
que hay una peticin HTTP, el servidor web (por lo general Apache) genera un
nuevo proceso solo para esta peticin, y empieza el script PHP indicado desde
cero, el cual es ejecutado de principio a fin.
As que respecto al control de flujo, estamos en el medio de nuestro programa en
Node.js, cuando una nueva peticin llega al puerto 8888: Cmo manipulamos
esto sin volvernos locos?
Bueno, esta es la parte donde el diseo orientado al evento de Node.js / JavaScript
de verdad ayuda, aunque tengamos que aprender nuevos conceptos para poder
dominarlo. Veamos como estos conceptos son aplicados en nuestro cdigo de
servidor.
Nosotros creamos el servidor, y pasamos una funcin al mtodo que lo crea. Cada
vez que nuestro servidor recibe una peticin, la funcin que le pasamos ser
llamada.
No sabemos qu es lo que va a suceder, pero ahora tenemos un lugar donde vamos

12 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

a poder manipular la peticin entrante. Es la funcin que pasamos, sin importar si


la definimos o si la pasamos de manera annima.
Este concepto es llamado un callback (del ingls: call = llamar; y back = de
vuelta). Nosotros pasamos una funcin a algn mtodo, y el mtodo ocupa esta
funcin para llamar (call) de vuelta (back) si un evento relacionado con este
mtodo ocurre.
Al menos para m, esto tom algn tiempo para ser entendido. Lee el articulo del
blog de Felix de nuevo si todava no te sientes seguro.
Juguemos un poco con este nuevo concepto. Podemos probar que nuestro cdigo
contina despus de haber creado el servidor, incluso si no ha sucedido ninguna
peticin HTTP y la funcin callback que pasamos no ha sido llamada? Probemos:
var http = require("http");
function onRequest(request, response) {
console.log("Peticion Recibida.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciado.");

Noten que utilizo console.log para entregar un texto cada vez que la funcin
onRequest (nuestro callback) es gatillada, y otro texto despus de iniciar nuestro
servidor HTTP.
Cuando iniciamos esta aplicacin (con node server.js, como siempre). Esta
inmediatamente escribir en pantalla "Servidor Iniciado" en la lnea de comandos.
Cada vez que hagamos una peticin a nuestro servidor (abriendo
http://localhost:8888/ en nuestro browser), el mensaje "Peticion Recibida." va a
ser impreso en la lnea de comandos.
Esto es JavaScript del Lado del Servidor Asincrnico y orientado al evento con
callbacks en accin :-)

13 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

(Toma en cuenta que nuestro servidor probablemente escribir "Peticin


Recibida." a STDOUT dos veces al abrir la pgina en un browser. Esto es porque la
mayora de los browsers van a tratar de cargar el favicon mediante la peticin
http://localhost:8888/favicon.ico cada vez que abras http://localhost:8888/).

Como nuestro Servidor manipula las peticiones


OK, Analicemos rpidamente el resto del cdigo de nuestro servidor, esto es, el
cuerpo de nuestra funcin de callback onRequest().
Cuando la Callback es disparada y nuestra funcin onRequest() es gatillada, dos
parmetros son pasados a ella: request y response.
Estos son objetos, y puedes usar sus mtodos para manejar los detalles de la
peticin HTTP ocurrida y responder a la peticin (en otras palabras enviar algo de
vuelta al browser que hizo la peticin a tu servidor).
Y eso es lo que nuestro cdigo hace: Cada vez que una peticin es recibida, usa la
funcin response.writeHead() para enviar un estatus HTTP 200 y un
content-type (parmetro que define que tipo de contenido es) en el encabezado de
la respuesta HTTP, y la funcin response.write() para enviar el texto "Hola
Mundo" en el cuerpo de la respuesta,
Por ltimo, nosotros llamamos response.end() para finalizar nuestra respuesta
Hasta el momento, no nos hemos interesado por los detalles de la peticin, y ese
es el porqu no hemos ocupado el objeto request completamente.

Encontrando un lugar para nuestro mdulo de


servidor
OK, promet que volveramos a al Cmo organizar nuestra aplicacin. Tenemos el
cdigo de nuestro servidor HTTP muy bsico en el archivo server.js, y mencion
que es comn tener un archivo principal llamado index.js, el cual es usado para
arrancar y partir nuestra aplicacin haciendo uso de los otros mdulos de la
aplicacin (como el mdulo de servidor HTTP que vive en server.js).
Hablemos de como podemos hacer que nuestro server.js sea un verdadero mdulo

14 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Node.js y que pueda ser usado por nuestro pronto-a-ser-escrito archivo principal
index.js.
Como habrn notado, ya hemos usado mdulos en nuestro cdigo, como ste:
var http = require("http");
...
http.createServer(...);

En algn lugar dentro de Node.js vive un mdulo llamado "http", y podemos hacer
uso de ste en nuestro propio cdigo requirindolo y asignando el resultado del
requerimiento a una variable local.
Esto transforma a nuestra variable local en un objeto que acarrea todos los
mtodos pblicos que el mdulo http provee.
Es prctica comn elegir el nombre del mdulo como nombre para nuestra
variable local, pero somos libres de escoger cualquiera que nos guste:
var foo = require("http");
...
foo.createServer(...);

Bien. Ya tenemos claro como hacer uso de los mdulos internos de Node.js.
Cmo hacemos para crear nuestros propios mdulos, y Cmo los utilizamos?
Descubrmoslo transformando nuestro script server.js en un mdulo real.
Sucede que, no tenemos que transformarlo tanto. Hacer que algn cdigo sea un
Mdulo, significa que necesitamos exportar las partes de su funcionalidad que
queremos proveer a otros scripts que requieran nuestro mdulo.
Por ahora, la funcionalidad que nuestro servidor HTTP necesita exportar es
simple: Permitir a los scripts que utilicen este mdulo arrancar el servidor.

15 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Para hacer esto posible, Dotaremos al cdigo de nuestro servidor de una funcin
llamada inicio, y exportaremos esta funcin:
var http = require("http");
function iniciar() {
function onRequest(request, response) {
console.log("Peticin Recibida.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciado.");
}
exports.iniciar = iniciar;

De este modo, Podemos crear nuestro propio archivo principal index.js, y arrancar
nuestro servidor HTTP all, aunque el cdigo para el servidor este en nuestro
archivo server.js.
Crea un archivo index.js con el siguiente contenido:
var server = require("./server");
server.iniciar();

Como puedes ver, nosotros utilizamos nuestro mdulo de servidor tal como
cualquier otro mdulo interno: requiriendo el archivo donde est contenido y
asignndolo a una variable, con las funciones que tenga 'exportadas' disponibles
para nosotros.
Eso es. Podemos ahora arrancar nuestra aplicacin por medio de nuestro script
principal, y va a hacer exactamente lo mismo:

node index.js

Bien, ahora podemos poner las diferentes partes de nuestra aplicacin en archivos

16 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

diferentes y enlazarlas juntas a travs de la creacin de estos mdulos.


Tenemos slo la primera parte de nuestra aplicacin en su lugar: Podemos recibir
peticiones HTTP. Pero necesitamos hacer algo con ellas - necesitamos reaccionar
de manera diferente, dependiendo de que URL el browser requiera de nuestro
servidor.
Para una aplicacin muy simple, podras hacer esto directamente dentro de una
funcin de callback OnRequest(). Pero, como dije, agreguemos un poco ms de
abstraccin, de manera de hacer nuestra aplicacin ms interesante.
Hacer diferentes peticiones HTTP ir a partes diferentes de nuestro cdigo se llama
"ruteo" (routing, en ingls) - bueno, entonces, cremos un mdulo llamado router.

Qu se necesita para "rutear" peticiones?


Necesitamos ser capaces de entregar la URL requerida y los posibles parmetros
GET o POST adicionales a nuestro router, y basado en estos, el router debe ser
capaz de decidir qu cdigo ejecutar (este "cdigo a ejecutar" es la tercera parte de
nuestra aplicacin: una coleccin de manipuladores de peticiones que harn el
verdadero trabajo cuando una peticin es recibida).
As que, Necesitamos mirar en las peticiones HTTP y extraer la URL requerida, as
como los parmetros GET/POST de ellos. Se puede discutir acerca de si este
procedimiento debe ser parte del router o del servidor (o si lo hacemos un mdulo
por s mismo), pero hagamos el acuerdo de hacerlo parte de nuestro servidor
HTTP por ahora.
Toda la informacin que necesitamos est disponible en el objeto request, el que
es pasado como primer parmetro a nuestra funcin callback onRequest(). Pero
para interpretar esta informacin, necesitamos algunos mdulos adicionales
Node.js, llamados url y querystring.
El mdulo url provee mtodos que nos permite extraer las diferentes partes de
una URL (como por ejemplo la ruta requerida y el string de consulta), y
querystring puede, en cambio, ser usado para parsear el string de consulta para
los parmetros requeridos:

17 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

url.parse(string).query
|
url.parse(string).pathname
|
|
|
|
|
------ ------------------http://localhost:8888/iniciar?foo=bar&hello=world
------|
|
|
|
querystring(string)["foo"]
|
|
querystring(string)["hello"]

Podemos, por supuesto, tambin utilizar querystring para parsear el cuerpo de


una peticin POST en busca de parmetros, como veremos ms tarde.
Agreguemos ahora a nuestra funcin onRequest() la lgica requerida para
encontrar que ruta URL el browser solicit:

var http = require("http");


var url = require("url");
function iniciar() {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Peticin para " + pathname + " recibida.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciado.");
}
exports.iniciar = iniciar;

Muy Bien. Nuestra aplicacin puede ahora distinguir peticiones basadas en la ruta
URL requerida - esto nos permite mapear peticiones hacia nuestro manipuladores
de peticiones, basndonos en la ruta URL usando nuestro (pronto a ser escrito)
router. Luego, podemos construir nuestra aplicacin en una forma REST
(RESTful way en Ingls), ya que ahora podemos implementar una interfaz que
sigue los principios que guan a la Identificacin de Recursos (ve por favor el
artculo de Wikipedia acerca de la Transferencia del Estado Representacional para
informacin de trasfondo.

18 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

En el contexto de nuestra aplicacin, esto significa simplemente que seremos


capaces de tener peticiones para las URLs /iniciar y /subir manejadas por partes
diferentes de nuestro cdigo. Veremos pronto como todo esto encaja.
OK, es hora de escribir nuestro router. Vamos a crear un nuevo archivo llamado
router.js, con el siguiente contenido:
function route(pathname) {
console.log("A punto de rutear una peticion para " + pathname);
}
exports.route = route;

Por supuesto, este cdigo no est haciendo nada, pero eso est bien por ahora.
Empecemos a ver como vamos a encajar este router con nuestro servidor antes de
poner ms lgica en el router.
Nuestro servidor HTTP necesita saber y hacer uso de nuestro router. Podemos
escribir directamente esta dependencia a nuestro servidor, pero como hemos
aprendido de la manera difcil en nuestras experiencias, vamos a acoplar de
manera dbil (loose coupling en Ingls) al router y su servidor va inyeccin por
dependencia. Para una referencia de fondo, leer el Artculo de Martin Fowler (en
Ingls).
Primero extendamos nuestra funcin iniciar() de manera de permitirnos pasar la
funcin de ruteo a ser usada como parmetro:
var http = require("http");
var url = require("url");
function iniciar(route) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Peticion para " + pathname + " recibida.");
route(pathname);
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciado.");

19 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

}
exports.iniciar = iniciar;

Y extendamos nuestro index.js adecuadamente, esto es, inyectando la funcin de


ruteo de nuestro router en el servidor:
var server = require("./server");
var router = require("./router");
server.iniciar(router.route);

Nuevamente , estamos pasando una funcin como parmetros, pero esto ya no es


una novedad para nosotros.
Si arrancamos nuestra aplicacin ahora (node index.js como siempre), y hacemos
una peticin para una URL, puedes ver ahora por las respuestas de la aplicacin
que nuestro servidor HTTP hace uso de nuestro router y le entrega el nombre de
ruta requerido:
bash$ node index.js
Peticin para /foo recibida.
A punto de rutear una peticion para /foo

He omitido la molesta respuesta de la peticin para /favicon.ico

Ejecucin en el reino de los verbos


Puedo divagar un vez ms por un momento y hablar acerca de la programacin
funcional de nuevo?
Pasar funciones no es slo una consideracin Tcnica. Con respecto al diseo de
software, esto es casi filosfico. Tan solo piensa en ello: en nuestro archivo de
index, podramos haber entregado el objeto router al servidor, y el servidor
hubiese llamado a la funcin route de este objeto.

20 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

De esta manera, podramos haber pasado una


, y el servidor hubiese usado
esa cosa para hacer algo. Oye, "Cosa Router", Podras por favor rutear esto por
m?
Pero el servidor no necesita la cosa. Slo necesita hacer algo, y para que algo se
haga, no necesitas cosas para nada, slo necesitas acciones. No necesitas
sustantivos, sino que necesitas verbos.
Entender este cambio de mentalidad fundamental que est en el ncleo de esta
idea es lo que realmente me hizo entender la programacin funcional.
Y lo entend mientras lea la obra maestra de Steve Yegge (en Ingls) Ejecucin en
el Reino de los Sustantivos. Anda, lela, por favor. Es uno de los mejores artculos
relacionados con el software que haya tenido el placer de encontrar.

Ruteando a los verdaderos manipuladores de


peticiones
Volviendo al tema. Nuestro servidor HTTP y nuestro router de peticiones son
ahora los mejores amigos y conversan entre ellos, tal y como pretendimos.
Por supuesto, esto no es suficiente, "Rutear" significa que nosotros queremos
manipular las peticiones a distintas URLs de manera, diferente. Nos gustara
tener la "lgicas de negocios" para peticiones de /inicio manejadas en otra
funcin, distinta a la que maneja las peticiones para /subir.
Por ahora, el ruteo "termina" en el router, y el router no es el lugar donde se est
"haciendo algo" con las peticiones, ya que esto no escalara bien una vez que
nuestra aplicacin se haga ms compleja.
Llamemos a estas funciones, donde las peticiones estn siendo ruteadas,
manipuladores de peticiones ( request handlers). Y procedamos con stos ahora,
porque, a menos que no los tengamos en su lugar, no hay tiene mucho sentido en
hacer nada con el router por ahora.
Nueva parte de la aplicacin, significa nuevo mdulo - no creo que haya sorpresa
ac. Creemos un mdulo llamado requestHandlers (por manipuladores de
peticin), agreguemos un funcin de ubicacin para cada manipulador de
peticin, y exportemos estos como mtodos para el mdulo:
21 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

function iniciar() {
console.log("Manipulador de peticin 'iniciar' ha sido llamado.");
}
function subir() {
console.log("Manipulador de peticin 'subir' ha sido llamado.");
}
exports.iniciar = iniciar;
exports.subir = subir;

Esto nos permitir atar los manipuladores de peticin al router, dndole a nuestro
router algo que rutear.
Llegado a este punto, necesitamos tomar una decisin: Ingresaremos las rutas
del mdulo requestHandlers dentro del cdigo del router (hard-coding), o
queremos algo ms de dependencia por inyeccin? Aunque en la dependencia por
inyeccin, como cualquier otro patrn, no debera ser usada simplemente por
usarla, en este caso tiene sentido acoplar el router dbilmente a sus
manipuladores de peticin, as, de esta manera hacemos que el router sea
reutilizable.
Esto significa que necesitamos pasar los manipuladores de peticin desde nuestro
server al router, pero esto se siente equivocado, dado que, Por Qu tenemos que
hacer el camino largo y entregar los manipuladores desde el archivo principal al
servidor y de ah al router?
Cmo vamos a pasarlos? Ahora tenemos slo dos manipuladores, pero en una
aplicacin real, este nmero se va a incrementar y variar, y nosotros no queremos
estar a cada momento mapeando peticiones a manipuladores cada vez que una
nueva URL o manipulador de peticin sea agregado. Y si tenemos un cdigo del
tipo if peticion == x then llama manipulador y en el router, esto se pondra cada
vez ms feo.
Un nmero variable de tems, cada uno de ellos mapeados a un string? (en este
caso la URL requerida) Bueno, esto suena como que un array asociativo hara el
truco.
Bueno, este descubrimiento es obscurecido por el hecho que JavaScript no provee
arrays asociativos - o s? !Resulta que lo que necesitamos usar son objetos si
necesitamos un array asociativo!

22 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Una buena introduccin a esto est (en Ingls) en http://msdn.microsoft.com


/en-us/magazine/cc163419.aspx, Djame citarte la parte relevante:
En C++ o C#, cuando hablamos acerca de objetos, nos estamos
refiriendo a instancias de clases de estructuras. Los objetos tienen
distintas propiedades y mtodos, dependiendo en las plantillas (esto
es, las clases) desde donde stos sean instanciados. Este no es el caso
con los objetos de JavaScript. En JavaScript, los objetos son slo
colecciones de pares nombre/valor - piensa en un objeto JavaScript
como en un diccionario con llaves de string.
Si los objetos JavaScript son slo colecciones de pares nombre/valor, Cmo
pueden entonces tener mtodos? Bueno, los valores pueden ser strings, nmeros,
etc... O Funciones!
OK, Ahora, volviendo finalmente al cdigo. Hemos decidido que queremos pasar
la lista de requestHandlers (manipuladores de peticin) como un objeto, y para
lograr este acoplamiento dbil, necesitamos usar la tcnica de inyectar este objeto
en la route() (ruta).
Empecemos con poner el objeto en nuestro archivo principal index.js:
var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");
var handle = {}
handle["/"] = requestHandlers.iniciar;
handle["/iniciar"] = requestHandlers.iniciar;
handle["/subir"] = requestHandlers.subir;
server.iniciar(router.route, handle);

(N. del T.: Se Opta por dejar los verbos en Ingls 'route' para rutear y 'handle' para
manipular).
Aunque handle es ms una "cosa" (una coleccin de manipuladores de peticin),
Propongo que lo nombremos como un verbo, ya que esto resultar en una
expresin fluida en nuestro router, como veremos a continuacin:
Como puedez ver, es realmente simple mapear diferentes URLs al mismo

23 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

manipulador de peticiones: Mediante la adicin de un par llave/valor de "/" y


requestHandlers.iniciar, podemos expresar en una forma agradable y limpia que
no slo peticiones a /start, sino que tambin peticiones a / pueden ser manejadas
por el manipulador inicio.
Despus de definir nuestro objeto, se lo pasamos al servidor como un parmetro
adicional. Modifiquemos nuestro server.js para hacer uso de este:
var http = require("http");
var url = require("url");
function iniciar(route, handle) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Peticion para " + pathname + " recibida.");
route(handle, pathname);
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Mundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciar.");
}
exports.iniciar = iniciar;

Lo que hacemos aqu, es chequear si un manipulador de peticiones para una ruta


dada existe, y si es as, simplemente llamamos a la funcin adecuada. Dado que
podemos acceder a nuestras funciones manipuladoras de peticin desde nuestro
objeto de la misma manera que hubisemos podido acceder a un elemento de un
array asociativo, es que tenemos la expresin fluida handle[pathname](); de la
que habl antes, que en otras palabras es: "Por favor, handle (maneja) este(a)
pathname (ruta)".
Bien, Esto es todo lo que necesitamos para atar servidor, router y manipuladores
de peticiones juntos! Una vez que arranquemos nuestra aplicacin y hagamos una
peticin en nuestro browser de http://localhost:8888/iniciar, vamos a probar que
el manipulador de peticin correcto fue, de hecho, llamado:

Servidor Iniciado.

24 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Peticion para /iniciar recibida.


A punto de rutear una peticin para /iniciar
Manipulador de peticion 'iniciar' ha sido llamado.

Haciendo que los Manipuladores de Peticiones


respondan
Muy bien. Ahora, si tan solo los manipuladores de peticin pudieran enviar algo
de vuelta al browser, esto sera mucho mejor, cierto?
Recuerda, que el "Hola Mundo" que tu browser despliega ante una peticin de una
pgina, an viene desde la funcin onRequest en nuestro archivo server.js.
"Manipular Peticiones" no significa otra cosa que "Responder a las Peticiones"
despus de todo, as que necesitamos empoderar a nuestros manipuladores de
peticiones para hablar con el browser de la misma manera que la funcin
onRequest lo hace.

Cmo no se debe hacer esto?


La aproximacin directa que nosotros - desarrolladores con un trasfondo en PHP
o Ruby - quisieramos seguir es de hecho conducente a errores: Trabaja de manera
espectacular al principio y parece tener mucho sentido, y de pronto, las cosas se
arruinan en el momento menos esperado.
A lo que me refiero con "aproximacin directa" es esto: hacer que los
manipuladores de peticin retornen - return() - el contenido que ellos quieran
desplegar al usuario, y luego, enviar esta data de respuesta en la funcin
onRequest de vuelta al usuario.
Tan slo hagamos esto, y luego, veamos por qu esto no es tan buena idea.
Empecemos con los manipuladores de peticin y hagmoslos retornar, lo que
nosotros queremos desplegar en el browser. Necesitamos modificar
requestHandlers.js a lo siguiente:
function iniciar() {

25 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

console.log("Manipulador de peticion 'iniciar' fue llamado.");


return "Hola Iniciar";
}
function subir() {
console.log("Manipulador de peticion 'subir' fue llamado.");
return "Hola Subir";
}
exports.iniciar = iniciar;
exports.subir = subir;

Bien. De todas maneras, el router necesita retornar al servidor lo que los


manipuladores de peticin le retornaron a l. Necesitamos entonces editar
router.js de esta manera:

function route(handle, pathname) {


console.log("A punto de rutear una peticion para " + pathname);
if (typeof handle[pathname] === 'function') {
return handle[pathname]();
} else {
console.log("No se encontro manipulador para " + pathname);
return "404 No Encontrado";
}
}
exports.route = route;

Como puedes ver, nosotros tambin retornaremos algn texto si la peticin no es


ruteada.
Por ltimo, pero no menos importante, necesitamos refactorizar nuestro servidor
para hacerlo responder al browser con el contenido que los manipuladores de
peticin le retornaron via el router, transformando de esta manera a server.js en:
var http = require("http");
var url = require("url");
function iniciar(route, handle) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Peticion para " + pathname + " recibida.");
response.writeHead(200, {"Content-Type": "text/html"});
var content = route(handle, pathname)

26 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

response.write(content);
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciado.");
}
exports.iniciar = iniciar;

Si nosotros arrancamos nuestra aplicacin re-escrita, todo va a funcionar a las mil


maravillas: Hacerle una peticin a http://localhost:8888/iniciar resulta en "Hola
Iniciar" siendo desplegado en el browser, hacerle una peticin a
http://localhost:8888/subir nos da "Hola Subir", y la peticin a
http://localhost:8888/foo produce "404 No Encontrado".
OK, entonces Por Qu esto es un problema? La respuesta corta es: debido a que
si uno de los manipuladores de peticin quisiera hacer uso de una operacin
no-bloqueante (non-blocking) en el futuro, entonces esta configuracin, como la
tenemos, sera problemtica.
Tommosnos algn tiempo para la respuesta larga.

Bloqueante y No-Bloqueante
Como se dijo, los problemas van a surgir cuando nosotros incluyamos operaciones
no-bloqueantes en los manipuladores de peticin. Pero hablemos acerca de las
operaciones bloqueantes primero, luego, acerca de las operaciones
no-bloqueantes.
Y, en vez de intentar explicar que es lo que significa "bloqueante" y "no
bloqueante", demostremos nosotros mismo que es lo que sucede su agregamos
una operacin bloqueante a nuestros manipuladores de peticin.
Para hacer esto, modificaremos nuestro manipulador de peticin iniciar para
hacer una espera de 10 segundos antes de retornar su string "Hola Iniciar". Ya que
no existe tal cosa como sleep() en JavaScript, usaremos un hack ingenioso para
ello.
Por favor, modifica requestHandlers.js como sigue:

27 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

function iniciar() {
console.log("Manipulador de peticion 'iniciar' fue llamado.");
function sleep(milliSeconds) {
// obten la hora actual
var startTime = new Date().getTime();
// atasca la cpu
while (new Date().getTime() < startTime + milliSeconds);
}
sleep(10000);
return "Hola Iniciar";
}
function subir() {
console.log("Manipulador de peticion 'subir' fue llamado.");
return "Hola Subir";
}
exports.iniciar = iniciar;
exports.subir = subir;

Dejemos claros que es lo que esto hace: Cuando la funcin iniciar() es llamada,
Node.js espera 10 segundos y slo ah retorna "Hola Iniciar". Cuando est
llamando a subir(), retorna inmediatamente, la misma manera que antes.
(Por supuesto la idea es que te imagines que, en vez de dormir por 10 segundos,
exista una operacin bloqueante verdadera en iniciar(), como algn tipo de
calculo de largo aliento.)
Vemos qu{e es lo que este cambio hace.
Como siempre, necesitamos reiniciar nuestro servidor. Esta vez, te pido sigas un
"protocolo" un poco ms complejo de manera de ver que sucede: Primero, abre
dos ventanas de browser o tablas. En la primera ventana, por favor ingresa
http://localhost:8888/iniciar en la barra de direcciones, pero no abras an esta
url!
En la barra de direcciones de la segunda ventana de browser, ingresa
http://localhost:8888/subir y, nuevamente, no presiones enter todava.
Ahora, haz lo siguiente: presiona enter en la primera ventana ("/iniciar"), luego,
rpidamente cambia a la segunda ventana ("/subir") y presiona enter, tambin.
Lo que veremos ser lo siguiente: La URL /inicio toma 10 segundos en cargar, tal
28 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

cual esperamos. pero la URL /subir tambin toma 10 segundos para cargar,
Aunque no hay definido un sleep() en el manipulador de peticiones
correspondiente!
Por Qu? simple, porque inicio() contiene una operacin bloqueante. En otras
palabras "Est bloqueando el trabajo de cualquier otra cosa".
He ah el problema, porque, el dicho es: "En Node.js, todo corre en paralelo,
excepto tu cdigo".
Lo que eso significa es que Node.js puede manejar un montn de temas
concurrentes, pero no lo hace dividiendo todo en hilos (threads) - de hecho,
Node.js corre en un slo hilo. En vez de eso, lo hace ejecutando un loop de
eventos, y nosotros, los desarrolladores podemos hacer uso de esto - Nosotros
debemos evitar operaciones bloqueantes donde sea posible, y utilizar operaciones
no-bloqueantes en su lugar.
Lo que exec() hace, es que, ejecuta un commando de shell desde dentro de
Node.js. En este ejemplo, vamos a usarlo para obtener una lista de todos los
archivos del directorio en que nos encontramos ("ls -lah"), permitindonos
desplegar esta lista en el browser de un usuario que este peticionando la URL
/inicio.
Lo que el cdigo hace es claro: Crea una nueva variable content() (con el valor
incial de "vacio"), ejecuta "ls -lah", llena la variable con el resultado, y lo retorna.
Como
siempre,
arrancaremos
http://localhost:8888/iniciar.

nuestra

aplicacin

visitaremos

Lo que carga una bella pgina que despliega el string "vacio". Qu es lo que est
incorrecto ac?
Bueno, como ya habrn adivinado, exec() hace su magia de una manera
no-bloqueante. Buena cosa esto, porque de esta manera podemos ejecutar
operaciones de shell muy caras en ejecucin (como, por ejemplo, copiar archivos
enormes o cosas similares) sin tener que forzar a nuestra aplicacin a detenerse
como lo hizo la operacin sleep.

29 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

(Si quieres probar esto, reemplaza "ls -lah" con una operacin ms cara como
"find /").
Pero no estaramos muy felices si nuestra elegante aplicacin no bloqueante no
desplegara algn resultado, cierto?.
Bueno, entonces, arreglmosla. Y mientras estamos en eso, tratemos de entender
por qu la arquitectura actual no funciona.
El problema es que exec(), para poder trabajar de manera no-bloqueante, hace uso
de una funcin de callback.
En nuestro ejemplo, es una funcin annima, la cual es pasada como el segundo
parmetro de la llamada a la funcin exec():

function (error, stdout, stderr) {


content = stdout;
}

Y aqu yace la raz de nuestro problema: Nuestro cdigo es ejecutado de manera


sincrnica, lo que significa que inmediatamente despus de llamar a exec(),
Node.js contina ejecutando return content;. En este punto, content todava est
vaco, dado el hecho que la funcin de callback pasada a exec() no ha sido an
llamada - porque exec() opera de manera asincrnica.
Ahora, "ls -lah" es una operacin sencilla y rpida (a menos, claro, que hayan
millones de archivos en el directorio). Por lo que es relativamente expedto llamar
al callback - pero de todas maneras esto sucede de manera asincrnica.
Esto se hace ms obvio al tratar con un comando ms costoso: "find /" se toma un
minuto en mi maquina, pero si reemplazo "ls -lah" con "find /" en el manipulador
de peticiones, yo recibo inmediatamente una respuesta HTTP cuando abro la URL
/inicio - est claro que exec() hace algo en el trasfondo, mientras que Node.js
mismo contina con el flujo de la aplicacin, y podemos asumir que la funcin de
callback que le entregamos a exec() ser llamada ssolo cuando el comando "find
/" haya terminado de correr.
Pero, Cmo podemos alcanzar nuestra meta, la de mostrarle al usuario una lista

30 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

de archivos del directorio actual?


Bueno, despus de aprender como no hacerlo, discutamos cmo hacer que
nuestros manipuladores de peticin respondan a los requirimientos del browser
de la manera correcta.

Respondiendo a los Manipuladores de Peticin con


Operaciones No Bloqueantes
Acabo de usar la frase "la manera correcta". Cosa Peligrosa. Frecuentemente, no
existe una nica "manera correcta".
Pero una posible solucin para esto, frecuente con Node.js es pasar funciones
alrededor. Examinemos esto.
Ahora mismo, nuestra aplicacin es capaz de transportar el contenido desde los
manipuladores de peticin al servidor HTTP retornndolo hacia arriba a travs de
las capas de la aplicacin (manipulador de peticin -> router -> servidor).
Nuestro nuevo enfoque es como sigue: en vez de llevar el contenido al servidor,
llevaremos el servidor al contenido. Para ser ms precisos, inyectaremos el objeto
response (respuesta) (desde nuestra funcin de callback de servidor onRequest())
a travs de nuestro router a los manipuladores de peticin. Los manipuladores
sern capaces de usar las funciones de este objeto para responder a las peticiones
ellos mismos.
Suficientes explicaciones, aqu hay una receta paso a paso de como cambiar
nuestra aplicacin.
Empecemos con nuestro servidor, server.js:
var http = require("http");
var url = require("url");
function iniciar(route, handle) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
route(handle, pathname, response);

31 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciado.");
}
exports.iniciar = iniciar;

En vez de esperar un valor de respuesta desde la funcin route(), pasmosle un


tercer parmetro: nuestro objeto response. Es ms, removamos cualquier llamada
a response desde el manipulador onRequest(), ya que ahora esperamos que route
se haga cargo de esto.
Ahora viene router.js:

function route(handle, pathname, response) {


console.log("About to route a request for " + pathname);
if (typeof handle[pathname] === 'function') {
handle[pathname](response);
} else {
console.log("No request handler found for " + pathname);
response.writeHead(404, {"Content-Type": "text/html"});
response.write("404 Not found");
response.end();
}
}
exports.route = route;

Mismo patrn: En vez de esperar que retorne un valor desde nuestros


manipuladores de peticin, nosotros traspasamos el objeto response.
Si no hay manipulador de peticin para utilizar, ahora nos hacemos cargo de
responder con adecuados encabezado y cuerpo "404".
Y por ltimo, pero no menos importante, modificamos a requestHandlers.js (el
archivo de manipuladores de peticin).
var exec = require("child_process").exec;
function iniciar(response) {
console.log("Manipulador de peticin 'iniciar' fue llamado.");

32 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

exec("ls -lah", function (error, stdout, stderr) {


response.writeHead(200, {"Content-Type": "text/html"});
response.write(stdout);
response.end();
});
}
function upload(response) {
console.log("Manipulador de peticin 'subir' fue llamado.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Subir");
response.end();
}
exports.iniciar = iniciar;
exports.subir = subir;

Nuestras funciones manipuladoras necesitan aceptar el parmetro de respuesta


response, y de esta manera, hacer uso de l de manera de responder a la peticin
directamente.
El manipulador iniciar responder con el callback annimo exec(), y el
manipulador subir replicar simplemente con "Hola Subir", pero esta vez,
haciendo uso del objeto response.
Si arrancamos nuestra aplicacin de nuevo (node index.js), esto debera funcionar
de acuerdo a lo esperado.
Si quieres probar que la operacin cara dentro de /iniciar no bloquear ms las
peticiones para /subir que sean respondidas inmediatamente, entonces modifica
tu archivo requestHandlers.js como sigue:

var exec = require("child_process").exec;


function iniciar(response) {
console.log("Manipulador de peticin 'iniciar' fue llamado.");
exec("find /",
{ timeout: 10000, maxBuffer: 20000*1024 },
function (error, stdout, stderr) {
response.writeHead(200, {"Content-Type": "text/html"});
response.write(stdout);
response.end();
});
}
function subir(response) {

33 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

console.log("Manipulador de peticin 'subir' fue llamado.");


response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Subir");
response.end();
}
exports.iniciar = iniciar;
exports.subir = subir;

Esto har que las peticiones HTTP a http://localhost:8888/iniciar tomen al


menos 10 segundos, pero, las peticiones a http://localhost:8888/subir sean
respondidas inmediatamente, incluso si /iniciar todava est en proceso.

Sirviendo algo til


Hasta ahora, lo que hemos hecho es todo simptico y bonito, pero no hemos
creado an valor para los clientes de nuestro sitio web ganador de premios.
Nuestro servidor, router y manipuladores de peticin estn en su lugar, as que
ahora podemos empezar a agregar contenido a nuestro sitio que permitir a
nuestros usuarios interactuar y andar a travs de los casos de uso de elegir un
archivo, subir este archivo, y ver el archivo subido en el browser. Por simplicidad
asumiremos que slo los archivos de imagen van a ser subidos y desplegados a
travs de la aplicacin.
OK, vemoslo paso a paso, pero ahora, con la mayora de las tcnicas y principios
de JavaScript explicadas, acelermoslo un poco al mismo tiempo.
Aqu, paso a paso significa a grandes razgos dos pasos: Vamos a ver primero como
manejar peticiones POST entrantes (pero no subidas de archivos), y en un
segundo paso, haremos uso de un modulo externo de Node.js para la
manipulacin de subida de archivos. He escogido este alcance por dos razones:
Primero, manejar peticiones POST bsicas es relativamente simple con Node.js,
pero an nos ensea lo suficiente para que valga la pena ejercitarlo.
Segundo, manejar las subidas de archivos (i.e. peticiones POST multiparte) no es
simple con Node.js, consecuentemente est ms all del alcance de este tutorial,
pero el aprender a usar un modulo externo es una leccin en s misma que tiene
sentido de ser includa en un tutorial de principiantes.

34 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Manejando Peticiones POST


Mantengamos esto ridculamente simple: Presentaremos un rea de texto que
pueda ser llenada por el usuario y luego enviada al servidor en una peticin POST.
Una vez recibida y manipulada esta peticin, despliegaremos el contenido del rea
de texto.
El HTML para el formulario de esta rea de texto necesita ser servida por nuestro
manipulador de peticin /iniciar, as que agregumoslo de inmediato. En el
archivo requestHandlers.js:

function iniciar(response) {
console.log("Manipulador de peticiones 'iniciar' fue llamado.");
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/subir" method="post">'+
'<textarea name="text" rows="20" cols="60"></textarea>'+
'<input type="submit" value="Submit text" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}
function upload(response) {
console.log("Manipulador de peticiones 'subir' fue llamado.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Hola Subir");
response.end();
}
exports.iniciar = iniciar;
exports.subir = subir;

Ahora, si esto no va a ganar los Webby Awards, entonces no se que podra.


Haciendo la peticin http://localhost:8888/iniciar en tu browser, deberas ver un
formulario muy simple. Si no, entonces probablemente no has reiniciado la
aplicacin.

35 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

Te estoy escuchando: Tener contenido de vista justo en el manipulador de peticin


es feo. Sin embargo, he decidido no incluir ese nivel extra de abstraccin (esto es,
separar la lgica de vista y controlador) en este tutorial, ya que pienso que es no
nos ensea nada que valga la pena saber en el contexto de JavaScript o Node.js.
Mejor usemos el espacio que queda en pantalla para un problema ms interesante,
esto es, manipular la peticin POST que dar con nuestro manipulador de peticin
/subir cuando el usuario enve este formulario.
Ahora que nos estamos convirtiendo en "novicios expertos", ya no nos sorprende
el hecho que manipular informacin de POST este hecho de una manera no
bloqueante, mediante el uso de llamadas asincrnicas.
Lo que tiene sentido, ya que las peticiones POST pueden ser potencialmente muy
grandes - nada detiene al usuario de introducir texto que tenga muchos megabytes
de tamao. Manipular este gran volumen de informacin de una vez puede
resultar en una operacin bloqueante.
Para hacer el proceso completo no bloqueante. Node.js le entrega a nuestro cdigo
la informacin POST en pequeos trozos con callbacks que son llamadas ante
determinados eventos. Estos eventos son data (un nuevo trozo de informacin
POST ha llegado) y end (todos los trozos han sido recibidos).
Necesitamos decirle a Node.js que funciones llamar de vuelta cuando estos
eventos ocurran. Esto es hecho agregando listeners (N. del T.: Del verbo listen escuchar) al objeto de peticin (request) que es pasado a nuestro callback
onRequest cada vez que una peticin HTTP es recibida.
Esto bsicamente luce as:

request.addListener("data", function(chunk) {
// funcion llamada cuando un nuevo trozo (chunk)
// de informacion (data) es recibido.
});
request.addListener("end", function() {
// funcion llamada cuando todos los trozos (chunks)
// de informacion (data) han sido recibidos.
});

La pregunta que surge es dnde implementar sta lgica. Nosotros slo podemos
36 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

acceder al objeto request en nuestro servidor - no se lo estamos pasando al router


o a los manipuladores de peticin, como lo hicimos con el objeto response.
En mi opinin, es un trabajo del servidor HTTP de darle a la aplicacin toda la
informacin de una peticin que necesite para hacer su trabajo. Luego, sugiero
que manejemos el procesamiento de la peticin de POST en el servidor mismo y
pasemos la informacin final al router y a los manipuladores de peticin, los que
luego decidirn que hacer con sta.
Entonces, la idea es poner los callbacks data y end en el servidor, recogiendo todo
los trozos de informacin POST en el callback data, y llamando al router una vez
recibido el evento end, mientras le entregamos los trozos de informacin
recogidos al router, el que a su vez se los pasa a los manipuladores de peticin.
Aqu vamos, empezando con server.js:
var http = require("http");
var url = require("url");
function iniciar(route, handle) {
function onRequest(request, response) {
var dataPosteada = "";
var pathname = url.parse(request.url).pathname;
console.log("Peticion para " + pathname + " recibida.");
request.setEncoding("utf8");
request.addListener("data", function(trozoPosteado) {
dataPosteada += trozoPosteado;
console.log("Recibido trozo POST '" + trozoPosteado + "'.");
});
request.addListener("end", function() {
route(handle, pathname, response, dataPosteada);
});
}
http.createServer(onRequest).listen(8888);
console.log("Servidor Iniciado");
}
exports.iniciar = iniciar;

Bsicamente hicimos tres cosas aqu: Primero, definimos que esperamos que la
codificacin de la informacin recibida sea UTF-8, agregamos un listener de

37 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

eventos para el evento "data" el cual llena paso a paso nuestra variable
dataPosteada cada vez que un nuevo trozo de informacin POST llega, y
movemos la llamada desde nuestro router al callback del evento end de manera de
asegurarnos que slo sea llamado cuando toda la informacin POST sea reunida.
Adems, pasamos la informacin POST al router, ya que la vamos a necesitar en
nuestros manipuladores de eventos.
Agregar un loggueo de consola cada vez que un trozo es recibido es una mala idea
para cdigo de produccin (megabytes de informacin POST, recuerdan?, pero
tiene sentido para que veamos que pasa.
Mejoremos nuestra aplicacin. En la pgina /subir, desplegaremos el contenido
recibido. Para hacer esto posible, necesitamos pasar la dataPosteada a los
manipuladores de peticin, en router.js.
function route(handle, pathname, response, postData) {
console.log("A punto de rutear una peticion para " + pathname);
if (typeof handle[pathname] === 'function') {
handle[pathname](response, postData);
} else {
console.log("No se ha encontrado manipulador para " + pathname);
response.writeHead(404, {"Content-Type": "text/html"});
response.write("404 No encontrado");
response.end();
}
}
exports.route = route;

Y en requestHandlers.js, inclumos la informacin de nuestro manipulador de


peticin subir:
function iniciar(response, postData) {
console.log("Manipulador de Peticion 'iniciar' fue llamado.");
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/subir" method="post">'+
'<textarea name="text" rows="20" cols="60"></textarea>'+
'<input type="submit" value="Submit text" />'+

38 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

'</form>'+
'</body>'+
'</html>';
response.writeHead(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}
function subir(response, dataPosteada) {
console.log("Manipulador de Peticion 'subir' fue llamado.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Tu enviaste: " + dataPosteada);
response.end();
}
exports.start = start;
exports.upload = upload;

Eso es, ahora somos capaces de recibir informacin POST y usarla en nuestros
manipuladores de peticin.
Una ltima cosa para este tema: Lo que le estamos pasando al router y los
manipuladores de peticin es el cuerpo (body) de nuestra peticin POST.
Probablemente necesitemos consumir los campos individuales que conforman la
informacin POST, en este caso, el valor del campo text.
Nosotros ya hemos ledo acerca del mdulo querystring, el que nos ayuda con
esto:
var querystring = require("querystring");
function iniciar(response, postData) {
console.log("Manipulador de peticion 'inicio' fue llamado.");
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/subir" method="post">'+
'<textarea name="text" rows="20" cols="60"></textarea>'+
'<input type="submit" value="Submit text" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200, {"Content-Type": "text/html"});

39 de 40

15/05/14 12:14

El Libro para Principiantes en Node.js Un tutorial c...

http://www.nodebeginner.org/index-es.html

response.write(body);
response.end();
}
function subir(response, dataPosteada) {
console.log("Manipulador de peticion 'subir' fue llamado.");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("Tu enviaste el texto: : " +
querystring.parse(dataPosteada)["text"]);
response.end();
}
exports.iniciar = iniciar;
exports.subir = subir;

Bueno, para un tutorial de principiantes, esto es todo lo que diremos acerca de la


informacin POST.
La prxima vez, hablaremos sobre como usar el excelente mdulo
node-formidable para permitirnos lograr nuestro caso de uso final: subir y
desplegar imgenes.

The Node Beginner Book by Manuel Kiessling (see Google+ prole) is licensed under a
Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
Permissions beyond the scope of this license may be available at manuel@kiessling.net.

40 de 40

15/05/14 12:14

Você também pode gostar