Vous êtes sur la page 1sur 40

24/2/2017 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.
http://www.nodebeginner.org/indexes.html 1/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Estructuradeestedocumento
AlTrminodeestedocumento,habrscreadounaaplicacinWebcompleta,que
permitaalosusuariosdestaelverpginaswebysubirarchivos.

La cual, por supuesto, no va ser nada como la "aplicacin que va a cambiar el


mundo",noobstanteeso,nosotrosharemosalgomsynovamossloacodificar
unaaplicacinlo"suficientementesimple"parahacerestoscasosdeusoposible,
sinoquecrearemosunframeworksencillo,perocompleto,afindepoder separar
los distintos aspectos de nuestra aplicacin. Vers lo que esto significa en poco
tiempo.

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.

Segn lo prometido, aprenderemos sobre la marcha acerca de algunos de los


muchosconceptosavanzadosdeJavaScript,comohacerusodeellos,yverelpor
qutienesentidoelhacerusodeestosconceptosenvezdelosqueyaconocemos
porotroslenguajesdeprogramacin.

TabladeContenidos

JavaScriptyNode.js

JavaScriptyT
Antesquehablemosdetodalapartetcnica,tommonosunminutoyhablemos
acerca de ti y tu relacin con JavaScript. Este captulo est aqu para permitirte
estimarsitienesentidoelquesigasonoleyendoestedocumento.
http://www.nodebeginner.org/indexes.html 2/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.

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
aplicacionesNode.jsesunacosaEntenderelporquellasnecesitanserescritas
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.

http://www.nodebeginner.org/indexes.html 3/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Miobjetivoesproveerteestagua.

UnaAdvertencia
HayalgunaspersonasrealmenteexcelenteenJavaScript.Nosoyunadeellas.

Yosoyrealmenteeltipodelquetehehabladoenlosprrafosprevios.Sunpar
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.

http://www.nodebeginner.org/indexes.html 4/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
haceresto:

console.log("HolaMundo");

Grabaelarchivo,yejectaloatravsdeNode.js:

nodeholamundo.js

EstedeberaretornarHolaMundoentumonitor.

Ok,estoesaburrido,deacuerdo?Asqueescribamosalgunacosareal.

UnaAplicacinWebCompletaconNode.js

LoscasosdeUso
Mantengmoslosimple,perorealista:

ElUsuariodeberasercapazdeocuparnuestraaplicacinconunbrowser.
ElUsuariodeberaverunapginadebienvenidacuandosolicita
http://dominio/inicio,lacualdespliegaunformulariodesubida.
Eligiendounarchivodeimagenparasubiryenviandoelformulario,la
imagendeberasersubidaahttp://dominio/subir,dondeesdesplegadauna
http://www.nodebeginner.org/indexes.html 5/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

vezquelasubidaestefinalizada.

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?

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.
http://www.nodebeginner.org/indexes.html 6/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
"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.

http://www.nodebeginner.org/indexes.html 7/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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


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.
http://www.nodebeginner.org/indexes.html 8/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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){
console.log(palabra);
}

functionejecutar(algunaFuncion,valor){
algunaFuncion(valor);
}

ejecutar(decir,"Hola");

Leeestocuidadosamente!Loqueestamoshaciendoaques,nosotrospasamosla
funcin decir() como el primer parmetro de la funcin ejecutar. No el valor de
retornodedecir,sinoquedecir()misma!

http://www.nodebeginner.org/indexes.html 9/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.

DeQumaneraelpasarfuncioneshaceque
nuestroservidorHTTPfuncione
Conesteconocimiento,VolvamosanuestroservidorHTTPminimalista:

http://www.nodebeginner.org/indexes.html 10/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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 por qu esto tiene sentido para nosotros, que queremos escribir
aplicacioneswebenNode.js.

http://www.nodebeginner.org/indexes.html 11/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

Cuandonosotrosllamamosalmtodohttp.createServer,porsupuestoquenoslo
queremosqueelservidorsequedeescuchandoenalgnpuerto,sinoquetambin
queremoshaceralgocuandohayunapeticinHTTPaesteservidor.

Elproblemaes,queestosucededemaneraasncrona:Puedesucederencualquier
momento,perosolotenemosunnicoprocesoenelcualnuestroservidorcorre.

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

http://www.nodebeginner.org/indexes.html 12/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

peticinHTTPylafuncincallbackquepasamosnohasidollamada?Probemos:

varhttp=require("http");

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 asncrono 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.

http://www.nodebeginner.org/indexes.html 13/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
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

http://www.nodebeginner.org/indexes.html 14/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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");

...

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;
http://www.nodebeginner.org/indexes.html 15/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
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, creemos un mdulo

http://www.nodebeginner.org/indexes.html 16/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
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"]

http://www.nodebeginner.org/indexes.html 17/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
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);
}

http://www.nodebeginner.org/indexes.html 18/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.");
}

exports.iniciar=iniciar;

Yextendamosnuestroindex.jsadecuadamente,estoes,inyectandolafuncinde
ruteodenuestrorouterenelservidor:

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

http://www.nodebeginner.org/indexes.html 19/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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


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.

http://www.nodebeginner.org/indexes.html 20/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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 21/40
24/2/2017 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.

Unnmerovariabledeitems,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

http://www.nodebeginner.org/indexes.html 22/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

colecciones de pares nombre/valor piensa en un objeto JavaScript


comoenundiccionarioconllavesdestring.

Si los objetos JavaScript son slo colecciones de pares nombre/valor, Cmo


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 puedes 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:

http://www.nodebeginner.org/indexes.html 23/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.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.writeHead(200,{"ContentType":"text/html"});
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

http://www.nodebeginner.org/indexes.html 24/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.

Cmonosedebehaceresto?
LaaproximacindirectaquenosotrosdesarrolladoresconuntrasfondoenPHP
oRubyquisiramosseguiresdehechoconducenteaerrores: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;

http://www.nodebeginner.org/indexes.html 25/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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
ruteada.

Porltimo,peronomenosimportante,necesitamosrefactorizarnuestroservidor
para hacerlo responder al browser con el contenido que los manipuladores de
peticinleretornaronvaelrouter,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://www.nodebeginner.org/indexes.html 26/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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


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.");
http://www.nodebeginner.org/indexes.html 27/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.)

Veamosquesloqueestecambiohace.

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!

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".

http://www.nodebeginner.org/indexes.html 28/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.

Loqueexec()hace,esque,ejecutauncomandodeshelldesdedentrodeNode.js.
Enesteejemplo,vamosausarloparaobtenerunalistadetodoslosarchivosdel
directorioenquenosencontramos("lslah"),permitindonosdesplegarestalista
enelbrowserdeunusuarioqueestepeticionandolaURL/inicio.

Lo que el cdigo hace es claro: crea una nueva variable content() (con el valor
incialde"vaco"),ejecuta"lslah",llenalavariableconelresultado,yloretorna.

Como siempre, arrancaremos nuestra aplicacin y visitaremos


http://localhost:8888/iniciar.

Loquecargaunabellapginaquedespliegaelstring"vaco".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
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.

http://www.nodebeginner.org/indexes.html 29/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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()operademaneraasncrona.

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 necesario
llamaralcallbackperodetodasmanerasestosucededemaneraasncrona.

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()serllamadaslocuandoelcomando"find/"
hayaterminadodecorrer.

Pero,cmopodemosalcanzarnuestrameta,lademostrarlealusuariounalista
dearchivosdeldirectorioactual?

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


nuestros manipuladores de peticin respondan a los requerimientosdelbrowser
delamaneracorrecta.

RespondiendoalosManipuladoresdePeticincon
OperacionesNoBloqueantes
Acabo de usar la frase "la manera correcta". Cosa peligrosa. Frecuentemente, no
existeunanica"maneracorrecta".

http://www.nodebeginner.org/indexes.html 30/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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;

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


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

Ahoravienerouter.js:

http://www.nodebeginner.org/indexes.html 31/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.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;

Nuestras funciones manipuladoras necesitan aceptar el parmetro de respuesta


response,ydeestamanera,hacerusodeldemaneraderesponderalapeticin

http://www.nodebeginner.org/indexes.html 32/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.
http://www.nodebeginner.org/indexes.html 33/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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,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,pasoapasosignificaagrandesrasgosdospasos: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
sentidodeserincluidaenuntutorialdeprincipiantes.

ManejandoPeticionesPOST
Mantengamos esto ridculamente simple: presentaremos un rea de texto que
puedaserllenadaporelusuarioyluegoenviadaalservidorenunapeticinPOST.
Unavezrecibidaymanipuladaestapeticin,desplegaremoselcontenidodelrea
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>'+

http://www.nodebeginner.org/indexes.html 34/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

'<body>'+
'<formaction="/subir"method="post">'+
'<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){
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,medianteelusodellamadasasncronas.

Loquetienesentido,yaquelaspeticionesPOSTpuedenserpotencialmentemuy
grandesnadadetienealusuariodeintroducirtextoquetengamuchosmegabytes
de tamao. Manipular este gran volumen de informacin de una vez puede
resultarenunaoperacinbloqueante.

http://www.nodebeginner.org/indexes.html 35/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.

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:

http://www.nodebeginner.org/indexes.html 36/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.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);
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.

Agregarunlogueodeconsolacadavezqueuntrozoesrecibidoesunamalaidea
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);
http://www.nodebeginner.org/indexes.html 37/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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, incluimos 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">'+
'<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.

http://www.nodebeginner.org/indexes.html 38/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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.");
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.

http://www.nodebeginner.org/indexes.html 39/40
24/2/2017 ElLibroparaPrincipiantesenNode.jsUntutorialcompletodenode.js

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

Imprint/Impressum/Datenschutz/Haftung

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

Vous aimerez peut-être aussi