Vous êtes sur la page 1sur 36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

ElLibroparaPrincipiantes
enNode.js

UntutorialdeNode.jspor:ManuelKiessling&HermanA.Junge

SobreelTutorial
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"sobrelamarcha.Estetutorialvamuchomsalldeltpico
manual"HolaMundo".

Status
Estsleyendolaversinfinaldeestelibro,esdecir,lasactualizacionessolosern
hechas para corregir errores o para reflejar cambiar en nuevas versiones de
Node.js.
Lasmuestrasdecdigodeestelibroestnprobadasparafuncionarconlaversin
0.6.11deNode.js.

AudienciaObjetivo
Estedocumentoprobablementesermejorentendidoporloslectoresquetengan
un trasfondo similar al mo: Programadores experimentados en al menos un
lenguaje orientado al objeto, como Ruby, Python, PHP o Java poca experiencia
conJavaScript,yningunaexperienciaenNode.js.
Elqueestedocumentoestorientadoadesarrolladoresqueyatienenexperiencia
con otros lenguajes de programacin significa que no vamos a cubrir temas
realmente bsicos como tipos de datos, variables, estructuras de control y
similares.Debessaberacercadeestostpicosparaentenderestedocumento.
Sinembargo,dadoquelasfuncionesyobjetosenJavaScriptsondiferentesdesus
contrapartes en la mayora de los lenguajes, estos sern explicados con ms
detalle.

Estructuradeestedocumento
http://www.nodebeginner.org/indexes.html

1/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

AlTrminodeestedocumento,habrscreadounaaplicacinWebcompleta,que
permitaalosusuariosdestaelverpginaswebysubirarchivos.
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
codificarunaaplicacinlo"suficientementesimple"parahacerestoscasosdeuso
posible,sinoquecrearemosunframeworksencillo,perocompleto,afindepoder
separarlosdistintosaspectosdenuestraaplicacin.Versloqueestosignificaen
pocotiempo.
EmpezaremospormirarcmoeldesarrolloenJavaScriptenNode.jsesdiferente
deldesarrolloenJavaScriptenunbrowser.
Luego,nosmantendremosconlaviejatradicindeescribirunaaplicacin"Hola
Mundo",lacualeslaaplicacinmsbsicadeNode.jsque"hace"algo.
Enseguida, discutiremos que tipo de "aplicacin del mundo real" queremos
construir, disectaremos las diferentes partes que necesitan ser implementadas
paraensamblarestaaplicacin,yempezaremostrabajando en cada una de estas
partespasoapaso.
Talycualloprometido,aprenderemossobrelamarchaacercadealgunosdelos
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
conocemosporotroslenguajesdeprogramacin.

TabladeContenidos

JavaScriptyNode.js
JavaScriptyT
Antesquehablemosdetodalapartetcnica,tommonosunminutoyhablemos
acerca de ti y tu relacin con JavaScript. Este captulo est aqu para permitirte
estimarsitienesentidoelquesigasonoleyendoestedocumento.
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,
agregandointeractividadatuspginasdecuandoencuando.
http://www.nodebeginner.org/indexes.html

2/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Lo que realmente quisiste era "la cosa real", queras saber cmo construir sitios
webcomplejosAprendisteunlenguajedeprogramacincomoPHP,Ruby,Java,
yempezasteaescribircdigo"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
avanzadasenlasTierrasdeJavaScript,yqueestelenguajeerarealmentemsque
hacerunwindow.open().
Sinembargo,estoeratodocosadelfrontend,yaunqueeraagradablecontarcon
jQuery a tu disposicin en cualquier momento que te sintieras con nimo de
sazonarunapginaweb,al final del da, lo que eras a lo ms, era un usuario de
JavaScript,perono,undesarrolladordeJavaScript.
YentoncesllegNode.js.JavaScriptenelservidor,Quhayconeso?
DecidistequeerayatiempoderevisarelnuevoJavaScript.Peroespera:Escribir
aplicaciones Node.js es una cosa Entender el porqu ellas necesitan ser escritas
enlamaneraquelosonsignificaentenderJavaScript!Yestavezesenserio.
Yaquestelproblema:YaqueJavaScriptrealmentevivedos,otalveztresvidas
(ElpequeoayudanteDHTMLdemediadosdelos90's,lascosasmsseriastales
comojQueryy similares, y ahora, el lado del servidor), no es tan fcil encontrar
informacinqueteayudeaaprenderJavaScriptdela"maneracorrecta",deforma
depoderescribiraplicacionesdeNode.jsenunaaparienciaquetehagasentirque
nosloestsusandoJavaScript,sinoquetambinestndesarrollandoconl.
Porque ah est el asunto: Ya eres un desarrollador experimentado,ynoquieres
aprender una nueva tcnica simplemente metiendo cdigo aqu y all mal
aprovechndolo Quieres estar seguro que te ests enfocando en un ngulo
correcto.
Hay,porsupuesto,excelentedocumentacinafuera.Peroladocumentacinpors
solanoessuficiente.Loquesenecesitaesunagua.
Miobjetivoesproveerteestagua.

UnaAdvertencia
HayalgunaspersonasrealmenteexcelenteenJavaScript.Nosoyunadeellas.
Yosoyrealmenteeltipodelquetehehabladoenlosprrafosprevios.Sunpar
http://www.nodebeginner.org/indexes.html

3/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
algunodelosaspectosavanzadosdeJavaScript.Nosoyexperimentado.
Porloqueestenoesunlibro"desdenoviciohastaexperto".Esteesmsbienun
libro"desdenovicioanovicioavanzado".
Si no fallo, entonces este ser el tipo de documento que deseo hubiese tenido
cuandoempecconNode.js.

JavaScriptdelLadodelServidor
LasprimerasencarnacionesdeJavaScriptvivanenlosbrowsers.Peroestoesslo
elcontexto.Defineloquepuedeshacerconellenguaje,peronodicemuchoacerca
deloqueellenguaje mismo puede hacer. JavaScript es un lenguaje "completo":
Lo puedes usar en muchos contextos y alcanzar con ste, todo lo que puedes
alcanzarconcualquierotrolenguaje"completo".
Node.jsrealmenteesslootrocontexto:tepermitecorrercdigoJavaScriptenel
backend,fueradelbrowser.
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,
haciendousodelaMaquinaVirtualV8deGoogle,elmismoentornodeejecucin
paraJavaScriptqueGoogleChromeutiliza.
Adems,Node.jsvieneconmuchosmdulostiles,demaneraquenotienes que
escribirtododecero,comoporejemplo,algoquepongaunstringalaconsola.
Entonces,Node.jsesenrealidaddoscosas:unentornodeejecucinyunalibrera.
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.PorfavorvuelveunavezquetengastuversindeNode.jscorriendo.

"HolaMundo"
Ok. Saltemos entonces al agua fra y escribamos nuestra primera aplicacin
Node.js:"HolaMundo".
Abre tu editor favorito y crea un archivo llamado holamundo.js. Nosotros
queremosescribir"HolaMundo"aSTDOUT,yaquestelcdigonecesariopara
http://www.nodebeginner.org/indexes.html

4/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

haceresto:

console.log("HolaMundo");

Grabaelarchivo,yejectaloatravsdeNode.js:

nodeholamundo.js

EstedeberaretornarHolaMundoentumonitor.
Ok,estoesaburrido,deacuerdo?Asqueescribamosalgunacosareal.

UnaAplicacinWebCompletaconNode.js
LoscasosdeUso
Mantengmoslosimple,perorealista:
ElUsuariodeberasercapazdeocuparnuestraaplicacinconunbrowser.
ElUsuariodeberaverunapginadebienvenidacuandosolicita
http://dominio/inicio,lacualdespliegaunformulariodesbida.
Eligiendounarchivodeimagenparasubiryenviandoelformulario,la
imagendeberasersubidaahttp://dominio/subir,dondeesdesplegadauna
vezquelasbidaestefinalizada.

Muy bien. Ahora, tu puedes ser capaz de alcanzar este objetivo googleando y
programandoloquesea,peroesonoesloquequeremoshaceraqu.
Msqueeso,noqueremosescribirsimplementeelcdigomsbsicoposiblepara
alcanzaresteobjetivo,noimportaloeleganteycorrectoquepuedaserestecdigo.
Nosotros agregaremos intencionalmente ms abstraccin de la necesaria de
maneradepodertenerunaideadeloqueesconstruiraplicacionesmscomplejas
deNode.js.

LaPiladeAplicaciones
Hagamos un desglose a nuestra aplicacin. Qu partes necesitan ser
implementadasparapodersatisfacernuestroscasosdeuso?
http://www.nodebeginner.org/indexes.html

5/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Queremosservirpginasweb,demaneraquenecesitamosunServidor
HTTP.
Nuestroservidornecesitarresponderdirectamentepeticiones(requests),
dependiendodequURLseapedidaenesterequerimiento,esque
necesitaremosalgntipodeenrutador(router)demanerademapearlos
peticionesaloshandlers(manejadores)destos.
Parasatisfaceralospeticionesquellegaronalservidoryhansidoruteados
usandoelenrutador,necesitaremosdehechohandlers(manejadores)
depeticiones
ElEnrutadorprobablementedeberatratarcualquierinformacinPOSTque
llegueydrselaaloshandlersdepeticionesenunaformaconveniente,luego
necesitaremosmanipulacindedatadepeticin
NosotrosnosoloqueremosmanejarpeticionesdeURLs,sinoquetambin
queremosdesplegarcontenidocuandoestasURLsseanpedidas,loque
significaquenecesitamosalgntipodelgicaenlasvistasaserutilizada
porloshandlersdepeticiones,demaneradepoderenviarcontenidoal
browserdelUsuario.
Porltimo,peronomenosimportante,elUsuariosercapazdesubir
imgenes,asquenecesitaremosalgntipodemanipulacindesubidas
quienseharcargodelosdetalles.

Pensemos un momento acerca de como construiramos esta pila de aplicaciones


con PHP. No es exactamente un secreto que la configuracin tpica sera un
ApacheHTTPserverconmod_php5instalado.
Loque,asuvez,significaqueeltema"Necesitamossercapacesdeservirpginas
webyrecibirpeticionesHTTP"nisiquierasucededentrodePHPmismo.
Bueno,conNode.js,lascosassonunpocodistintas.PorqueconNode.js,nosolo
implementamos nuestra aplicacin, nosotros tambin implementamos todo el
servidorHTTPcompleto.Dehecho,nuestraaplicacinwebysuservidorwebson
bsicamentelomismo.
Esto puede sonar como mucho trabajo, pero veremos en un momento que con
Node.js,noloes.
Empecemosporelprincipioeimplementemoslaprimerapartedenuestrapila,el
servidorHTTP..

ConstruyendolaPiladeAplicaciones
UnServidorHTTPBsico
CuandollegualpuntodondequeraempezarconmiprimeraaplicacinNode.js
http://www.nodebeginner.org/indexes.html

6/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

"real", me pregunt no solo como la iba a programar, sino que tambin, como
organizarmicdigo.
Necesitar tenerlo todo en un archivo? Muchos tutoriales en la Web que te
enseancmoescribirunservidorHTTPbsicoenNode.jstienentodalalgica
enunsololugar.Qupasasiyoquieroasegurarmequemicdigosemantenga
lebleamedidaquelevayaagregandomscosas?
Resulta,queesrelativamentefcildemantenerlosdistintosaspectosdetucdigo
separados,ponindolosenmdulos.
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.
Miimpresinesqueesmsomenosunestndarnombraratuarchivoprincipal
comoindex.js.Tienesentidotambinquepongamosnuestromdulodeservidor
enunarchivollamadoserver.js.
Empecemosconelmdulodelservidor.Creaelarchivoserver.jseneldirectorio
razdetuproyecto,yllnaloconelcdigosiguiente:

varhttp=require("http");
http.createServer(function(request,response){
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaMundo");
response.end();
}).listen(8888);

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


testendolo.PrimeroejecutatuscriptconNode.js:

nodeserver.js

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


desplegarunapginawebquediga"HolaMundo".
Interesante, no? Qu tal si hablamos de que est pasando aqu y dejamos la
http://www.nodebeginner.org/indexes.html

7/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

pregunta de 'cmo organizar nuestro proyecto' para despus? Prometo que


volveremosaesto.

AnalizandonuestroservidorHTTP
Bueno,entonces,analicemosqueestpasandoaqu.
Laprimeralnearequire,requierealmdulohttpquevieneincluidoconNode.jsy
lohaceaccesibleatravsdelavariablehttp.
Luegollamamosaunadelasfuncionesqueelmdulohttpofrece: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
nuestroservidorHTTPvaaescuchar.
Porfavorignoraporunsegundoaladefinicindefuncinquesiguealallavede
aperturadehttp.createServer.
Nosotrospodramoshaberescritoelcdigoqueiniciaanuestroservidorylohace
escucharalpuerto8888delasiguientemanera:

varhttp=require("http");
varserver=http.createServer();
server.listen(8888);

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


nadams(nisiquierarespondidoalgunapeticinentrante).
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
unoesperaraelprimerparmetrodelallamadaacreateServer().
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
puedenserpasadasdeunladoaotrocomocualquierotrovalor.

PasandoFuncionesdeunLadoaOtro
Puedes,porejemplo,haceralgocomoesto:

functiondecir(palabra){
http://www.nodebeginner.org/indexes.html

8/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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

Leeestocuidadosamente!Loqueestamoshaciendoaques,nosotrospasamosla
funcindecir() como el primer parmetro de la funcin ejecutar. No el valor de
retornodedecir,sinoquedecir()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()(agregandollaves).
Por supuesto, dado que decir toma un parmetro, ejecutar puede pasar tal
parmetrocuandollamaaalgunaFuncion.
Nosotrospodemos,talcomolohicimos,pasarunafuncinpor su nombre como
parmetroaotrafuncin.Peronoestamosobligadosatenerquedefinirlafuncin
primero y luego pasarla. Podemos tambin definir y pasar la funcin como un
parmetroaotrafuncintodoalmismotiempo:

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

(N.delT.:functionesunapalabraclavedeJavaScript).
Nosotrosdefinimoslafuncinquequeremospasaraejecutarjustoahenellugar
dondeejecutaresperasuprimerparmetro.
De esta manera, no necesitamos darle a la funcin un nombre, por lo que esta
funcinesllamadafuncinannima.
EstaesunaprimeraojeadaaloquemegustallamarJavaScript"avanzado".Pero
tommoslo paso a paso. Por ahora, aceptemos que en JavaScript, nosotros
podemospasarunafuncincomounparmetrocuandollamamosaotrafuncin.
Podemos hacer esto asignando nuestra funcin a una variable, la cual luego
pasamos,odefiniendolafuncinapasarenelmismolugar.
http://www.nodebeginner.org/indexes.html

9/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

DeQumaneraelpasarfuncioneshaceque
nuestroservidorHTTPfuncione
Conesteconocimiento,VolvamosanuestroservidorHTTPminimalista:

varhttp=require("http");
http.createServer(function(request,response){
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaMundo");
response.end();
}).listen(8888);

A estas alturas, debera quedar claro lo que estamos haciendo ac: Estamos
pasndolealafuncincreateServerunafuncinannima.
Podemosllegaralomismorefactorizandonuestrocdigoas:

varhttp=require("http");
functiononRequest(request,response){
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaMundo");
response.end();
}
http.createServer(onRequest).listen(8888);

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


estodeestamanera?

CallbacksManejadasporEventos
La respuesta a) No es algo fcil de explicar (al menos para m), y b) Yace en la
naturaleza misma de como Node.js trabaja: Est orientado al evento, esa es la
razndeporquestanrpido.
Podras tomarte un tiempo para leer este excelente post (en ingls) de Felix
Geisendrdfer:Understandingnode.jsparaalgunaexplicacindetrasfondo.
AlfinaltodosereducealhechoqueNode.jstrabajaorientadoalevento.Ah,ys,
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
aplicacioneswebenNode.js.
http://www.nodebeginner.org/indexes.html

10/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Cuandonosotrosllamamosalmtodohttp.createServer,porsupuestoquenoslo
queremosqueelservidorsequedeescuchandoenalgnpuerto,sinoquetambin
queremoshaceralgocuandohayunapeticinHTTPaesteservidor.
El problema es, que esto sucede de manera asincrnica: Puede suceder en
cualquier momento, pero solo tenemos un nico proceso en el cual nuestro
servidorcorre.
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,elcualesejecutadodeprincipioafin.
Asquerespectoalcontroldeflujo,estamosenelmediodenuestroprogramaen
Node.js, cuando una nueva peticin llega al puerto 8888: Cmo manipulamos
estosinvolvernoslocos?
Bueno,estaeslapartedondeeldiseoorientadoaleventodeNode.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.
Nosotroscreamoselservidor,ypasamosunafuncinalmtodoquelocrea.Cada
vez que nuestro servidor recibe una peticin, la funcin que le pasamos ser
llamada.
Nosabemosquesloquevaasuceder,peroahoratenemosunlugardondevamos
apodermanipularlapeticinentrante.Eslafuncinquepasamos,sinimportarsi
ladefinimososilapasamosdemaneraannima.
Esteconceptoesllamadouncallback(N.delT.:delingls:call=llamaryback=
devuelta).Nosotrospasamosunafuncinaalgnmtodo,yelmtodoocupaesta
funcin para llamar (call) de vuelta (back) si un evento relacionado con este
mtodoocurre.
Almenosparam,estotomalgntiempoparaserentendido.Leeelarticulodel
blogdeFelixdenuevositodavanotesientesseguro.
Juguemosunpococonestenuevoconcepto.Podemosprobarquenuestrocdigo
continadespusdehabercreadoelservidor,inclusosinohasucedidoninguna
peticinHTTPylafuncincallbackquepasamosnohasidollamada?Probemos:

varhttp=require("http");
http://www.nodebeginner.org/indexes.html

11/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

functiononRequest(request,response){
console.log("PeticionRecibida.");
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaMundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("ServidorIniciado.");

Noten que utilizo console.log para entregar un texto cada vez que la funcin
onRequest(nuestrocallback)esgatillada,yotrotextodespusdeiniciarnuestro
servidorHTTP.
Cuando iniciamos esta aplicacin (con node server.js, como siempre). Esta
inmediatamenteescribirenpantalla"ServidorIniciado"enlalneadecomandos.
Cada vez que hagamos una peticin a nuestro servidor (abriendo
http://localhost:8888/ennuestrobrowser),elmensaje"PeticionRecibida."vaa
serimpresoenlalneadecomandos.
Esto es JavaScript del lado del servidor asincrnico y orientado al evento con
callbacksenaccin:)
(Toma en cuenta que nuestro servidor probablemente escribir "Peticin
Recibida."aSTDOUTdosvecesalabrirlapginaenunbrowser.Estoesporquela
mayora de los browsers van a tratar de cargar el favicon mediante la peticin
http://localhost:8888/favicon.icocadavezqueabrashttp://localhost:8888/).

ComonuestroServidormanipulalaspeticiones
OK, analicemos rpidamente el resto del cdigo de nuestro servidor, esto es, el
cuerpodenuestrafuncindecallbackonRequest().
Cuando la callback es disparada y nuestra funcin onRequest() es gatillada, dos
parmetrossonpasadosaella:requestyresponse.
Estos son objetos, y puedes usar sus mtodos para manejar los detalles de la
peticinHTTPocurridayresponderalapeticin(enotraspalabrasenviaralgode
vueltaalbrowserquehizolapeticinatuservidor).
Yesoesloquenuestrocdigohace:cadavezqueunapeticinesrecibida,usala
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
http://www.nodebeginner.org/indexes.html

12/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

respuestaHTTP,ylafuncinresponse.write()paraenviareltexto"HolaMundo"
enelcuerpodelarespuesta.
Porltimo,nosotrosllamamosresponse.end()parafinalizarnuestrarespuesta.
Hastaelmomento,nonoshemosinteresadoporlosdetallesdelapeticin,yese
eselporqunohemosocupadoelobjetorequestcompletamente.

Encontrandounlugarparanuestromdulode
servidor
OK,prometquevolveramosaalCmoorganizarnuestraaplicacin.Tenemosel
cdigodenuestroservidorHTTPmuybsicoenelarchivoserver.js,ymencion
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(comoelmdulodeservidorHTTPqueviveenserver.js).
Hablemosdecomopodemoshacerquenuestroserver.jsseaunverdaderomdulo
Node.jsyquepuedaserusadopornuestroprontoaserescritoarchivoprincipal
index.js.
Comohabrnnotado,yahemosusadomdulosennuestrocdigo,comoste:

varhttp=require("http");
...
http.createServer(...);

EnalgnlugardentrodeNode.jsviveunmdulollamado"http",ypodemoshacer
uso de ste en nuestro propio cdigo requirindolo y asignando el resultado del
requerimientoaunavariablelocal.
Esto transforma a nuestra variable local en un objeto que acarrea todos los
mtodospblicosqueelmdulohttpprovee.
Es prctica comn elegir el nombre del mdulo como nombre para nuestra
variablelocal,perosomoslibresdeescogercualquieraquenosguste:

varfoo=require("http");
...

http://www.nodebeginner.org/indexes.html

13/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

foo.createServer(...);

Bien. Ya tenemos claro como hacer uso de los mdulos internos de Node.js.
Cmohacemosparacrearnuestrospropiosmdulos,yCmolosutilizamos?
Descubrmoslotransformandonuestroscriptserver.jsenunmduloreal.
Sucedeque,notenemosquetransformarlotanto.Hacerquealgncdigoseaun
Mdulo, significa que necesitamos exportar las partes de su funcionalidad que
queremosproveeraotrosscriptsquerequierannuestromdulo.
Por ahora, la funcionalidad que nuestro servidor HTTP necesita exportar es
simple:Permitiralosscriptsqueutilicenestemduloarrancarelservidor.
Parahacerestoposible,dotaremosalcdigodenuestroservidordeunafuncin
llamadainicio,yexportaremosestafuncin:

varhttp=require("http");
functioniniciar(){
functiononRequest(request,response){
console.log("PeticinRecibida.");
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaMundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("ServidorIniciado.");
}
exports.iniciar=iniciar;

Deestemodo,Podemoscrearnuestropropioarchivoprincipalindex.js,yarrancar
nuestro servidor HTTP all, aunque el cdigo para el servidor este en nuestro
archivoserver.js.
Creaunarchivoindex.jsconelsiguientecontenido:

varserver=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
http://www.nodebeginner.org/indexes.html

14/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

asignndolo a una variable, con las funciones que tenga 'exportadas' disponibles
paranosotros.
Eso es. Podemos ahora arrancar nuestra aplicacin por medio de nuestro script
principal,yvaahacerexactamentelomismo:

nodeindex.js

Bien,ahorapodemosponerlasdiferentespartesdenuestraaplicacinenarchivos
diferentesyenlazarlasjuntasatravsdelacreacindeestosmdulos.
Tenemosslolaprimerapartedenuestraaplicacinensulugar:Podemosrecibir
peticionesHTTP.Peronecesitamoshaceralgoconellasnecesitamos 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,demaneradehacernuestraaplicacinmsinteresante.
HacerdiferentespeticionesHTTPirapartesdiferentesdenuestrocdigosellama
"ruteo" (N. del T.: routing, en ingls) bueno, entonces, cremos un mdulo
llamadorouter.

Qusenecesitapara"rutear"peticiones?
NecesitamossercapacesdeentregarlaURLrequeridaylosposiblesparmetros
GET o POST adicionales a nuestro router, y basado en estos, el router debe ser
capazdedecidirqucdigoejecutar(este"cdigoaejecutar"eslatercerapartede
nuestra aplicacin: una coleccin de manipuladores de peticiones que harn el
verdaderotrabajocuandounapeticinesrecibida).
Asque,NecesitamosmirarenlaspeticionesHTTPyextraerlaURLrequerida,as
como los parmetros GET/POST de ellos. Se puede discutir acerca de si este
procedimientodebeserpartedelrouterodelservidor(osilohacemosunmdulo
por s mismo), pero hagamos el acuerdo de hacerlo parte de nuestro servidor
HTTPporahora.
Todalainformacinquenecesitamosestdisponibleenelobjetorequest,elque
es pasado como primer parmetro a nuestra funcin callback onRequest(). Pero
para interpretar esta informacin, necesitamos algunos mdulos adicionales
http://www.nodebeginner.org/indexes.html

15/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Node.js,llamadosurlyquerystring.
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
querystringpuede,encambio,serusadopara parsear el string de consulta para
losparmetrosrequeridos:

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


unapeticinPOSTenbuscadeparmetros,comoveremosmstarde.
Agreguemos ahora a nuestra funcin onRequest() la lgica requerida para
encontrarquerutaURLelbrowsersolicit:

varhttp=require("http");
varurl=require("url");
functioniniciar(){
functiononRequest(request,response){
varpathname=url.parse(request.url).pathname;
console.log("Peticinpara"+pathname+"recibida.");
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaMundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("ServidorIniciado.");
}
exports.iniciar=iniciar;

MuyBien.Nuestraaplicacinpuedeahoradistinguirpeticionesbasadasenlaruta
URLrequeridaestonospermitemapearpeticioneshacianuestromanipuladores
de peticiones, basndonos en la ruta URL usando nuestro (pronto a ser escrito)
router.Luego,podemos construir nuestra aplicacin en una forma REST (N. del
T.:RESTfulwayenIngls),yaqueahorapodemosimplementarunainterfazque
http://www.nodebeginner.org/indexes.html

16/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

sigue los principios que guan a la Identificacin de Recursos (ve por favor el
artculodeWikipediaacercadelaTransferenciadelEstadoRepresentacionalpara
informacindetrasfondo.
En el contexto de nuestra aplicacin, esto significa simplemente que seremos
capacesdetenerpeticionesparalasURLs/iniciary/subirmanejadasporpartes
diferentesdenuestrocdigo.Veremosprontocomotodoestoencaja.
OK,eshoradeescribirnuestrorouter.Vamosacrearunnuevoarchivollamado
router.js,conelsiguientecontenido:

functionroute(pathname){
console.log("Apuntoderutearunapeticionpara"+pathname);
}
exports.route=route;

Por supuesto, este cdigo no est haciendo nada, pero eso est bien por ahora.
Empecemosavercomovamosaencajaresterouterconnuestroservidorantesde
ponermslgicaenelrouter.
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
maneradbil(loosecouplingenIngls)al router y su servidor va inyeccin por
dependencia.Paraunareferenciadefondo,leerelArtculodeMartinFowler(en
Ingls).
Primeroextendamosnuestrafuncininiciar()demaneradepermitirnospasarla
funcinderuteoaserusadacomoparmetro:

varhttp=require("http");
varurl=require("url");
functioniniciar(route){
functiononRequest(request,response){
varpathname=url.parse(request.url).pathname;
console.log("Peticionpara"+pathname+"recibida.");
route(pathname);
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaMundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("ServidorIniciado.");
}
http://www.nodebeginner.org/indexes.html

17/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

exports.iniciar=iniciar;

Yextendamosnuestroindex.jsadecuadamente,estoes,inyectandolafuncinde
ruteodenuestrorouterenelservidor:

varserver=require("./server");
varrouter=require("./router");
server.iniciar(router.route);

Nuevamente,estamospasandounafuncincomoparmetros,peroestoyanoes
unanovedadparanosotros.
Siarrancamosnuestraaplicacinahora(nodeindex.jscomosiempre),yhacemos
unapeticinparaunaURL,puedesverahoraporlasrespuestasdela aplicacin
quenuestroservidorHTTPhaceusodenuestrorouteryleentregaelnombrede
rutarequerido:

bash$nodeindex.js
Peticinpara/foorecibida.
Apuntoderutearunapeticionpara/foo

Heomitidolamolestarespuestadelapeticinpara/favicon.ico

Ejecucinenelreinodelosverbos
Puedodivagarunvezmsporunmomentoyhablaracercadelaprogramacin
funcionaldenuevo?
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
hubiesellamadoalafuncinroutedeesteobjeto.
De esta manera, podramos haber pasado una cosa, y el servidor hubiese usado
esacosaparahaceralgo.Oye,"CosaRouter",Podrasporfavor rutear esto por
m?
Peroelservidornonecesitalacosa.Slonecesitahaceralgo,y para que algo se
http://www.nodebeginner.org/indexes.html

18/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

haga, no necesitas cosas para nada, slo necesitas acciones. No necesitas


sustantivos,sinoquenecesitasverbos.
Entender este cambio de mentalidad fundamental que est en el ncleo de esta
ideaesloquerealmentemehizoentenderlaprogramacinfuncional.
YloentendmientraslealaobramaestradeSteveYegge(enIngls)Ejecucinen
elReinodelosSustantivos.Anda,lela,porfavor.Esunodelosmejoresartculos
relacionadosconelsoftwarequehayatenidoelplacerdeencontrar.

Ruteandoalosverdaderosmanipuladoresde
peticiones
Volviendo al tema. Nuestro servidor HTTP y nuestro router de peticiones son
ahoralosmejoresamigosyconversanentreellos,talycomopretendimos.
Por supuesto, esto no es suficiente, "Rutear" significa que nosotros queremos
manipularlaspeticionesadistintasURLsdemanera,diferente.Nosgustaratener
la "lgicas de negocios" para peticiones de /inicio manejadas en otra funcin,
distintaalaquemanejalaspeticionespara/subir.
Porahora,elruteo"termina"enelrouter,yelrouternoesellugardondeseest
"haciendo algo" con las peticiones, ya que esto no escalara bien una vez que
nuestraaplicacinsehagamscompleja.
Llamemos a estas funciones, donde las peticiones estn siendo ruteadas,
manipuladoresdepeticiones(requesthandlers).Yprocedamosconstosahora,
porque,amenosquenolostengamosensulugar,nohaytienemuchosentidoen
hacernadaconelrouterporahora.
Nuevapartedelaaplicacin,significanuevomdulonocreoquehayasorpresa
ac.CreemosunmdulollamadorequestHandlers(N.delT.:pormanipuladores
de peticin), agreguemos un funcin de ubicacin para cada manipulador de
peticin,yexportemosestoscomomtodosparaelmdulo:

functioniniciar(){
console.log("Manipuladordepeticin'iniciar'hasidollamado.");
}
functionsubir(){
console.log("Manipuladordepeticin'subir'hasidollamado.");
}
exports.iniciar=iniciar;
exports.subir=subir;
http://www.nodebeginner.org/indexes.html

19/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Estonospermitiratarlosmanipuladoresdepeticinalrouter,dndoleanuestro
routeralgoquerutear.
Llegado a este punto, necesitamos tomar una decisin: Ingresaremos las rutas
del mdulo requestHandlers dentro del cdigo del router (hardcoding), o
queremosalgomsdedependenciaporinyeccin?Aunqueenladependenciapor
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.
Estosignificaquenecesitamospasarlosmanipuladoresdepeticindesdenuestro
serveralrouter,peroestosesienteequivocado,dadoque,Porqutenemosque
hacerelcaminolargoyentregarlosmanipuladoresdesde el archivo principal al
servidorydeahalrouter?
Cmo vamos a pasarlos? Ahora tenemos slo dos manipuladores, pero en una
aplicacinreal,estenmerosevaaincrementaryvariar,ynosotrosnoqueremos
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
tipoifpeticion==xthenllamamanipuladoryenelrouter,estosepondracada
vezmsfeo.
Unnmerovariabledetems,cadaunodeellosmapeadosaunstring?(en este
casolaURLrequerida)Bueno,estosuenacomoqueunarrayasociativoharael
truco.
Bueno,estedescubrimientoesobscurecidoporelhechoqueJavaScriptnoprovee
arrays asociativos o s? !Resulta que lo que necesitamos usar son objetos si
necesitamosunarrayasociativo!
Unabuenaintroduccinaestoest(enIngls)enhttp://msdn.microsoft.com/en
us/magazine/cc163419.aspx,Djamecitartelaparterelevante:
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,lasclases)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
comoenundiccionarioconllavesdestring.
Si los objetos JavaScript son slo colecciones de pares nombre/valor, Cmo
http://www.nodebeginner.org/indexes.html

20/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

puedenentoncestenermtodos?Bueno,losvalorespuedenserstrings,nmeros,
etc...OFunciones!
OK,ahora,volviendofinalmentealcdigo.Hemosdecididoquequeremospasarla
lista de requestHandlers (manipuladores de peticin) como un objeto, y para
lograresteacoplamientodbil,necesitamosusarlatcnicadeinyectaresteobjeto
enlaroute()(ruta).
Empecemosconponerelobjetoennuestroarchivoprincipalindex.js:

varserver=require("./server");
varrouter=require("./router");
varrequestHandlers=require("./requestHandlers");
varhandle={}
handle["/"]=requestHandlers.iniciar;
handle["/iniciar"]=requestHandlers.iniciar;
handle["/subir"]=requestHandlers.subir;
server.iniciar(router.route,handle);

(N.delT.:SeOptapordejarlosverbosenIngls'route'pararuteary'handle'para
manipular).
Aunquehandleesmsuna"cosa"(unacoleccindemanipuladoresde peticin),
propongo que lo nombremos como un verbo, ya que esto resultar en una
expresinfluidaennuestrorouter,comoveremosacontinuacin:
Como puedez ver, es realmente simple mapear diferentes URLs al mismo
manipulador de peticiones: Mediante la adicin de un par llave/valor de "/" y
requestHandlers.iniciar,podemosexpresarenunaformaagradableylimpiaque
noslopeticionesa/start,sinoquetambinpeticionesa/puedensermanejadas
porelmanipuladorinicio.
Despusdedefinirnuestroobjeto,selopasamosalservidorcomounparmetro
adicional.Modifiquemosnuestroserver.jsparahacerusodeeste:

varhttp=require("http");
varurl=require("url");
functioniniciar(route,handle){
functiononRequest(request,response){
varpathname=url.parse(request.url).pathname;
console.log("Peticionpara"+pathname+"recibida.");
route(handle,pathname);
response.writeHead(200,{"ContentType":"text/html"});
http://www.nodebeginner.org/indexes.html

21/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

response.write("HolaMundo");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("ServidorIniciado.");
}
exports.iniciar=iniciar;

Loquehacemosaqu,eschequearsiunmanipuladordepeticionesparaunaruta
dada existe, y si es as, simplemente llamamos a la funcin adecuada. Dado que
podemos acceder a nuestras funciones manipuladoras de peticin desde nuestro
objetodelamismamaneraquehubisemospodidoaccederaunelementodeun
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,Estoestodoloquenecesitamosparaatarservidor,routerymanipuladores
depeticionesjuntos!Unavezquearranquemosnuestraaplicacinyhagamosuna
peticinennuestrobrowserdehttp://localhost:8888/iniciar,vamosaprobarque
elmanipuladordepeticincorrectofue,dehecho,llamado:

ServidorIniciado.
Peticionpara/iniciarrecibida.
Apuntoderutearunapeticinpara/iniciar
Manipuladordepeticion'iniciar'hasidollamado.

HaciendoquelosManipuladoresdePeticiones
respondan
Muybien.Ahora,sitansololosmanipuladoresdepeticinpudieranenviaralgo
devueltaalbrowser,estoseramuchomejor,cierto?
Recuerda,queel"HolaMundo"quetubrowserdespliegaanteunapeticindeuna
pgina,anvienedesdelafuncinonRequestennuestroarchivoserver.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
onRequestlohace.

http://www.nodebeginner.org/indexes.html

22/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Cmonosedebehaceresto?
LaaproximacindirectaquenosotrosdesarrolladoresconuntrasfondoenPHP
oRubyquisieramosseguiresdehechoconducenteaerrores:trabajademanera
espectacularalprincipioyparecetenermuchosentido,y de pronto, las cosas se
arruinanenelmomentomenosesperado.
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
onRequestdevueltaalusuario.
Tanslohagamosesto,yluego,veamosporquestonoestanbuenaidea.
Empecemos con los manipuladores de peticin y hagmoslos retornar, lo que
nosotros queremos desplegar en el browser. Necesitamos modificar
requestHandlers.jsalosiguiente:

functioniniciar(){
console.log("Manipuladordepeticion'iniciar'fuellamado.");
return"HolaIniciar";
}
functionsubir(){
console.log("Manipuladordepeticion'subir'fuellamado.");
return"HolaSubir";
}
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.jsdeestamanera:

functionroute(handle,pathname){
console.log("Apuntoderutearunapeticionpara"+pathname);
if(typeofhandle[pathname]==='function'){
returnhandle[pathname]();
}else{
console.log("Noseencontromanipuladorpara"+pathname);
return"404NoEncontrado";
}
}
exports.route=route;

Comopuedesver,nosotrostambinretornaremosalgntextosilapeticinnoes
http://www.nodebeginner.org/indexes.html

23/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

ruteada.
Porltimo,peronomenosimportante,necesitamosrefactorizarnuestroservidor
para hacerlo responder al browser con el contenido que los manipuladores de
peticinleretornaronviaelrouter,transformandodeestamaneraaserver.jsen:

varhttp=require("http");
varurl=require("url");
functioniniciar(route,handle){
functiononRequest(request,response){
varpathname=url.parse(request.url).pathname;
console.log("Peticionpara"+pathname+"recibida.");
response.writeHead(200,{"ContentType":"text/html"});
varcontent=route(handle,pathname)
response.write(content);
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("ServidorIniciado.");
}
exports.iniciar=iniciar;

Sinosotrosarrancamosnuestraaplicacinreescrita,todovaafuncionaralasmil
maravillas:hacerleunapeticinahttp://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/fooproduce"404NoEncontrado".
OK,entoncesPorquestoesunproblema?Larespuestacortaes:debidoaquesi
uno de los manipuladores de peticin quisiera hacer uso de una operacin no
bloqueante (nonblocking) en el futuro, entonces esta configuracin, como la
tenemos,seraproblemtica.
Tommosnosalgntiempoparalarespuestalarga.

BloqueanteyNoBloqueante
Comosedijo,losproblemasvanasurgircuandonosotrosincluyamosoperaciones
nobloqueantes 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
http://www.nodebeginner.org/indexes.html

24/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

bloqueante", demostremos nosotros mismo que es lo que sucede su agregamos


unaoperacinbloqueanteanuestrosmanipuladoresdepeticin.
Para hacer esto, modificaremos nuestro manipulador de peticin iniciar para
hacerunaesperade10segundosantesderetornarsustring"HolaIniciar".Yaque
no existe tal cosa como sleep() en JavaScript, usaremos un hack ingenioso para
ello.
Porfavor,modificarequestHandlers.jscomosigue:

functioniniciar(){
console.log("Manipuladordepeticion'iniciar'fuellamado.");
functionsleep(milliSeconds){
//obtenlahoraactual
varstartTime=newDate().getTime();
//atascalacpu
while(newDate().getTime()<startTime+milliSeconds);
}
sleep(10000);
return"HolaIniciar";
}
functionsubir(){
console.log("Manipuladordepeticion'subir'fuellamado.");
return"HolaSubir";
}
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
llamandoasubir(),retornainmediatamente,lamismamaneraqueantes.
(Porsupuestolaideaesqueteimaginesque,envezdedormirpor10segundos,
exista una operacin bloqueante verdadera en iniciar(), como algn tipo de
calculodelargoaliento.)
Vemosquesloqueestecambiohace.
Comosiempre,necesitamosreiniciarnuestroservidor.Estavez,tepidosigasun
"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!

http://www.nodebeginner.org/indexes.html

25/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

En la barra de direcciones de la segunda ventana de browser, ingresa


http://localhost:8888/subiry,nuevamente,nopresionesentertodava.
Ahora, haz lo siguiente: presiona enter en la primera ventana ("/iniciar"), luego,
rpidamentecambiaalasegundaventana("/subir")ypresionaenter,tambin.
Loqueveremosserlosiguiente:LaURL/iniciotoma10segundosencargar,tal
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"Estbloqueandoeltrabajodecualquierotracosa".
He ah el problema, porque, el dicho es: "En Node.js, todo corre en paralelo,
exceptotucdigo".
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
debemosevitaroperacionesbloqueantesdondeseaposible,yutilizaroperaciones
nobloqueantesensulugar.
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
incialde"vacio"),ejecuta"lslah",llenalavariableconelresultado,yloretorna.
Como siempre, arrancaremos
http://localhost:8888/iniciar.

nuestra

aplicacin

visitaremos

Loquecargaunabellapginaquedespliegaelstring"vacio".Quesloqueest
incorrectoac?
Bueno, como ya habrn adivinado, exec() hace su magia de una manera no
bloqueante. Buena cosa esto, porque de esta manera podemos ejecutar
operacionesdeshellmuycarasenejecucin(como,porejemplo,copiar archivos
enormes o cosas similares) sin tener que forzar a nuestra aplicacin a detenerse
http://www.nodebeginner.org/indexes.html

26/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

comolohizolaoperacinsleep.
(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
desplegaraalgnresultado,cierto?.
Bueno,entonces,arreglmosla.Ymientrasestamoseneso,tratemosdeentender
porqulaarquitecturaactualnofunciona.
Elproblemaesqueexec(),parapodertrabajardemaneranobloqueante,haceuso
deunafuncindecallback.
Ennuestroejemplo,esunafuncinannima,lacualespasadacomoelsegundo
parmetrodelallamadaalafuncinexec():

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.jscontinaejecutandoreturncontent.Enestepunto,contenttodavaest
vaco, dado el hecho que la funcin de callback pasada a exec() no ha sido an
llamadaporqueexec()operademaneraasincrnica.
Ahora, "ls lah" es una operacin sencilla y rpida (a menos, claro, que hayan
millonesdearchivoseneldirectorio).Porloqueesrelativamenteexpedtollamar
alcallbackperodetodasmanerasestosucededemaneraasincrnica.
Estosehacemsobvioaltratarconuncomandomscostoso:"find/"setomaun
minutoenmimaquina,perosireemplazo"lslah"con"find/"enelmanipulador
depeticiones,yoreciboinmediatamenteunarespuestaHTTPcuandoabrolaURL
/inicio est claro que exec() hace algo en el trasfondo, mientras que Node.js
mismocontinaconelflujodelaaplicacin,ypodemosasumirquelafuncinde
callbackqueleentregamosaexec()serllamadassolocuandoelcomando"find
/"hayaterminadodecorrer.
Pero,cmopodemosalcanzarnuestrameta,lademostrarlealusuariounalista
dearchivosdeldirectorioactual?

http://www.nodebeginner.org/indexes.html

27/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Bueno, despus de aprender como no hacerlo, discutamos cmo hacer que


nuestros manipuladores de peticin respondan a los requirimientos del browser
delamaneracorrecta.

RespondiendoalosManipuladoresdePeticincon
OperacionesNoBloqueantes
Acabo de usar la frase "la manera correcta". Cosa peligrosa. Frecuentemente, no
existeunanica"maneracorrecta".
Pero una posible solucin para esto, frecuente con Node.js es pasar funciones
alrededor.Examinemosesto.
Ahora mismo, nuestra aplicacin es capaz de transportar el contenido desde los
manipuladoresdepeticinalservidorHTTPretornndolohaciaarribaatravsde
lascapasdelaaplicacin(manipuladordepeticin>router>servidor).
Nuestro nuevo enfoque es como sigue: en vez de llevar el contenido al servidor,
llevaremoselservidoralcontenido.Parasermsprecisos,inyectaremoselobjeto
response(respuesta)(desdenuestrafuncindecallbackdeservidoronRequest())
a travs de nuestro router a los manipuladores de peticin. Los manipuladores
serncapacesdeusarlasfuncionesdeesteobjetopararesponderalaspeticiones
ellosmismos.
Suficientes explicaciones, aqu hay una receta paso a paso de como cambiar
nuestraaplicacin.
Empecemosconnuestroservidor,server.js:

varhttp=require("http");
varurl=require("url");
functioniniciar(route,handle){
functiononRequest(request,response){
varpathname=url.parse(request.url).pathname;
console.log("Peticionpara"+pathname+"recibida.");
route(handle,pathname,response);
}
http.createServer(onRequest).listen(8888);
console.log("ServidorIniciado.");
}
exports.iniciar=iniciar;

http://www.nodebeginner.org/indexes.html

28/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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


tercerparmetro:nuestroobjetoresponse.Esms,removamoscualquierllamada
aresponsedesdeelmanipuladoronRequest(),yaqueahoraesperamosqueroute
sehagacargodeesto.
Ahoravienerouter.js:

functionroute(handle,pathname,response){
console.log("Apuntoderutearunapeticionpara"+pathname);
if(typeofhandle[pathname]==='function'){
handle[pathname](response);
}else{
console.log("Nohaymanipuladordepeticionpara"+pathname);
response.writeHead(404,{"ContentType":"text/html"});
response.write("404NoEncontrado");
response.end();
}
}
exports.route=route;

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


manipuladoresdepeticin,nosotrostraspasamoselobjetoresponse.
Si no hay manipulador de peticin para utilizar, ahora nos hacemos cargo de
responderconadecuadosencabezadoycuerpo"404".
Y por ltimo, pero no menos importante, modificamos a requestHandlers.js (el
archivodemanipuladoresdepeticin).

varexec=require("child_process").exec;
functioniniciar(response){
console.log("Manipuladordepeticin'iniciar'fuellamado.");
exec("lslah",function(error,stdout,stderr){
response.writeHead(200,{"ContentType":"text/html"});
response.write(stdout);
response.end();
});
}
functionsubir(response){
console.log("Manipuladordepeticin'subir'fuellamado.");
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaSubir");
response.end();
}
exports.iniciar=iniciar;
exports.subir=subir;

http://www.nodebeginner.org/indexes.html

29/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Nuestras funciones manipuladoras necesitan aceptar el parmetro de respuesta


response,ydeestamanera,hacerusodeldemaneraderesponderalapeticin
directamente.
El manipulador iniciar responder con el callback annimo exec(), y el
manipulador subir replicar simplemente con "Hola Subir", pero esta vez,
haciendousodelobjetoresponse.
Siarrancamosnuestraaplicacindenuevo(nodeindex.js),estodeberafuncionar
deacuerdoaloesperado.
Si quieres probar que la operacin cara dentro de /iniciar no bloquear ms las
peticionespara/subirqueseanrespondidas inmediatamente, entonces modifica
tuarchivorequestHandlers.jscomosigue:

varexec=require("child_process").exec;
functioniniciar(response){
console.log("Manipuladordepeticin'iniciar'fuellamado.");
exec("find/",
{timeout:10000,maxBuffer:20000*1024},
function(error,stdout,stderr){
response.writeHead(200,{"ContentType":"text/html"});
response.write(stdout);
response.end();
});
}
functionsubir(response){
console.log("Manipuladordepeticin'subir'fuellamado.");
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaSubir");
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
respondidasinmediatamente,inclusosi/iniciartodavaestenproceso.

Sirviendoalgotil
Hasta ahora, lo que hemos hecho es todo simptico y bonito, pero no hemos
creadoanvalorparalosclientesdenuestrositiowebganadordepremios.
Nuestro servidor, router y manipuladores de peticin estn en su lugar, as que
http://www.nodebeginner.org/indexes.html

30/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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,subirestearchivo,yverelarchivosubidoenelbrowser.Porsimplicidad
asumiremos que slo los archivos de imagen van a ser subidos y desplegados a
travsdelaaplicacin.
OK,vemoslopasoapaso,peroahora,conlamayoradelastcnicasyprincipios
deJavaScriptexplicadas,acelermoslounpocoalmismotiempo.
Aqu,pasoapasosignificaagrandesrazgosdospasos:vamosaverprimerocomo
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
manipulacindesubidadearchivos.Heescogidoestealcancepordosrazones:
Primero, manejar peticiones POST bsicas es relativamente simple con Node.js,
peroannosensealosuficienteparaquevalgalapenaejercitarlo.
Segundo,manejarlassubidasdearchivos(i.e.peticionesPOSTmultiparte)noes
simpleconNode.js,consecuentementeest ms all del alcance de este tutorial,
peroelaprenderausarunmoduloexternoesunaleccinensmismaquetiene
sentidodeserincludaenuntutorialdeprincipiantes.

ManejandoPeticionesPOST
Mantengamos esto ridculamente simple: presentaremos un rea de texto que
puedaserllenadaporelusuarioyluegoenviadaalservidorenunapeticinPOST.
Unavezrecibidaymanipuladaestapeticin,despliegaremoselcontenidodelrea
detexto.
ElHTMLparaelformulariodeestareadetextonecesitaserservidapornuestro
manipulador de peticin /iniciar, as que agregumoslo de inmediato. En el
archivorequestHandlers.js:

functioniniciar(response){
console.log("Manipuladordepeticiones'iniciar'fuellamado.");
varbody='<html>'+
'<head>'+
'<metahttpequiv="ContentType"content="text/html;
charset=UTF8"/>'+
'</head>'+
'<body>'+
'<formaction="/subir"method="post">'+
'<textareaname="text"rows="20"cols="60"></textarea>'+
'<inputtype="submit"value="Enviartexto"/>'+
'</form>'+
'</body>'+
'</html>';
http://www.nodebeginner.org/indexes.html

31/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

response.writeHead(200,{"ContentType":"text/html"});
response.write(body);
response.end();
}
functionsubir(response){
console.log("Manipuladordepeticiones'subir'fuellamado.");
response.writeHead(200,{"ContentType":"text/html"});
response.write("HolaSubir");
response.end();
}
exports.iniciar=iniciar;
exports.subir=subir;

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


Haciendolapeticinhttp://localhost:8888/iniciarentubrowser,deberasverun
formulario muy simple. Si no, entonces probablemente no has reiniciado la
aplicacin.
Teestoyescuchando:tenercontenidodevistajustoenelmanipuladordepeticin
esfeo.Sinembargo,hedecididonoincluiresenivelextradeabstraccin(estoes,
separarlalgicadevistaycontrolador)enestetutorial,yaquepiensoqueesno
nosenseanadaquevalgalapenasaberenelcontextodeJavaScriptoNode.js.
Mejorusemoselespacioquequedaenpantallaparaunproblemamsinteresante,
estoes,manipularlapeticinPOSTquedarconnuestromanipuladordepeticin
/subircuandoelusuarioenveesteformulario.
Ahoraquenosestamosconvirtiendoen"noviciosexpertos",yanonossorprende
el hecho que manipular informacin de POST este hecho de una manera no
bloqueante,medianteelusodellamadasasincrnicas.
Loquetienesentido,yaquelaspeticionesPOSTpuedenserpotencialmentemuy
grandesnadadetienealusuariodeintroducirtextoquetengamuchosmegabytes
de tamao. Manipular este gran volumen de informacin de una vez puede
resultarenunaoperacinbloqueante.
Parahacerelprocesocompletonobloqueante.Node.jsleentregaanuestrocdigo
la informacin POST en pequeos trozos con callbacks que son llamadas ante
determinados eventos. Estos eventos son data (un nuevo trozo de informacin
POSThallegado)yend(todoslostrozoshansidorecibidos).
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
onRequestcadavezqueunapeticinHTTPesrecibida.
http://www.nodebeginner.org/indexes.html

32/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Estobsicamenteluceas:

request.addListener("data",function(chunk){
//funcionllamadacuandounnuevotrozo(chunk)
//deinformacion(data)esrecibido.
});
request.addListener("end",function(){
//funcionllamadacuandotodoslostrozos(chunks)
//deinformacion(data)hansidorecibidos.
});

Lapreguntaquesurgeesdndeimplementarstalgica.Nosotrosslo podemos
accederalobjetorequestennuestroservidornoseloestamospasandoalrouter
oalosmanipuladoresdepeticin,comolohicimosconelobjetoresponse.
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
quemanejemoselprocesamientodelapeticindePOSTenelservidormismoy
pasemoslainformacinfinalalrouteryalosmanipuladoresdepeticin,losque
luegodecidirnquehacerconsta.
Entonces,laideaesponerloscallbacksdatayendenelservidor,recogiendotodo
lostrozosdeinformacinPOSTenelcallbackdata,yllamandoalrouterunavez
recibido el evento end, mientras le entregamos los trozos de informacin
recogidosalrouter,elqueasuvezselospasaalosmanipuladoresdepeticin.
Aquvamos,empezandoconserver.js:

varhttp=require("http");
varurl=require("url");
functioniniciar(route,handle){
functiononRequest(request,response){
vardataPosteada="";
varpathname=url.parse(request.url).pathname;
console.log("Peticionpara"+pathname+"recibida.");
request.setEncoding("utf8");
request.addListener("data",function(trozoPosteado){
dataPosteada+=trozoPosteado;
console.log("RecibidotrozoPOST'"+trozoPosteado+"'.");
});
request.addListener("end",function(){
route(handle,pathname,response,dataPosteada);
});
}
http.createServer(onRequest).listen(8888);
http://www.nodebeginner.org/indexes.html

33/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

console.log("ServidorIniciado");
}
exports.iniciar=iniciar;

Bsicamente hicimos tres cosas aqu: primero, definimos que esperamos que la
codificacin de la informacin recibida sea UTF8, agregamos un listener de
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
movemoslallamadadesdenuestrorouteralcallbackdeleventoenddemanerade
asegurarnosquesloseallamadocuandotodalainformacinPOSTseareunida.
Adems,pasamoslainformacinPOSTalrouter,yaquelavamosanecesitaren
nuestrosmanipuladoresdeeventos.
Agregarunloggueodeconsolacadavezqueuntrozoesrecibidoesunamalaidea
para cdigo de produccin (megabytes de informacin POST, recuerdan?, pero
tienesentidoparaqueveamosquepasa.
Mejoremos nuestra aplicacin. En la pgina /subir, desplegaremos el contenido
recibido. Para hacer esto posible, necesitamos pasar la dataPosteada a los
manipuladoresdepeticin,enrouter.js.

functionroute(handle,pathname,response,postData){
console.log("Apuntoderutearunapeticionpara"+pathname);
if(typeofhandle[pathname]==='function'){
handle[pathname](response,postData);
}else{
console.log("Nosehaencontradomanipuladorpara"+pathname);
response.writeHead(404,{"ContentType":"text/html"});
response.write("404Noencontrado");
response.end();
}
}
exports.route=route;

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


peticinsubir:

functioniniciar(response,postData){
console.log("ManipuladordePeticion'iniciar'fuellamado.");
varbody='<html>'+
'<head>'+
'<metahttpequiv="ContentType"content="text/html;
charset=UTF8"/>'+
'</head>'+
'<body>'+
'<formaction="/subir"method="post">'+
http://www.nodebeginner.org/indexes.html

34/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

'<textareaname="text"rows="20"cols="60"></textarea>'+
'<inputtype="submit"value="Enviartexto"/>'+
'</form>'+
'</body>'+
'</html>';

response.writeHead(200,{"ContentType":"text/html"});
response.write(body);
response.end();
}
functionsubir(response,dataPosteada){
console.log("ManipuladordePeticion'subir'fuellamado.");
response.writeHead(200,{"ContentType":"text/html"});
response.write("Tuenviaste:"+dataPosteada);
response.end();
}
exports.iniciar=iniciar;
exports.subir=subir;

Eso es, ahora somos capaces de recibir informacin POST y usarla en nuestros
manipuladoresdepeticin.
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.
Probablementenecesitemosconsumirloscamposindividualesqueconformanla
informacinPOST,enestecaso,elvalordelcampotext.
Nosotros ya hemos ledo acerca del mdulo querystring, el que nos ayuda con
esto:

varquerystring=require("querystring");
functioniniciar(response,postData){
console.log("Manipuladordepeticion'inicio'fuellamado.");
varbody='<html>'+
'<head>'+
'<metahttpequiv="ContentType"content="text/html;
charset=UTF8"/>'+
'</head>'+
'<body>'+
'<formaction="/subir"method="post">'+
'<textareaname="text"rows="20"cols="60"></textarea>'+
'<inputtype="submit"value="Submittext"/>'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200,{"ContentType":"text/html"});
response.write(body);
response.end();
}
functionsubir(response,dataPosteada){
console.log("Manipuladordepeticion'subir'fuellamado.");
http://www.nodebeginner.org/indexes.html

35/36

31/8/2015

ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

response.writeHead(200,{"ContentType":"text/html"});
response.write("Tuenviasteeltexto::"+
querystring.parse(dataPosteada)["text"]);
response.end();
}
exports.iniciar=iniciar;
exports.subir=subir;

Bueno,parauntutorialdeprincipiantes,estoestodoloquediremosacercadela
informacinPOST.
La prxima vez, hablaremos sobre como usar el excelente mdulo node
formidable para permitirnos lograr nuestro caso de uso final: subir y desplegar
imgenes.

TheNodeBeginnerBookbyManuelKiessling(seeGoogle+profile)islicensedundera
CreativeCommonsAttributionNonCommercialShareAlike3.0UnportedLicense.
Permissionsbeyondthescopeofthislicensemaybeavailableatmanuel@kiessling.net.

http://www.nodebeginner.org/indexes.html

36/36

Vous aimerez peut-être aussi