Anatomia de una app NodeJS

Es realmente sencillo poner en marcha el denominado "Hola Mundo" en Node. Sin embargo, el simple hecho de realizar esto va mas allá que escribir unas simples lineas de código, ya que en la esencia de Node, esta el "hazlo tu mismo", y por este mismo motivo, es necesario montar nuestro propio servidor http, con el fin de poder servir en un explorador web, este ambicioso primer programa.

Es por ello que, a diferencia de otros lenguajes, el "Hola Mundo" de node, va mas allá que simplemente invocar un prompt en pantalla, o escribir un string con la salida estándar.

Iniciar un proyecto Node.

Lo primero a realizar, es iniciar un proyecto de desarrollo NodeJS. Para realizar esto, se puede hacer de la forma "manual", o de la forma automatizada. Cuando digo manual, me refiero a crear los archivos y estructura de carpetas nosotros mismos, y cuando digo automático, me refiero a utilizar el asistente de NPM.

Cual conviene?

Realmente, es indistinto, pero con el asistente de NPM ahorramos mucho tiempo y la verdad no tenemos ningún tipo de ganancia con respecto a hacerlo manualmente.

Es recomendable crear una carpeta en donde se va a guardar dicha app, en este ejemplo se va a llamar "ejemplo1"

El asistente de NPM se invoca con el comando:

npm init  

Lo cual nos devolverá por salida estándar (consola) una serie de preguntas:

  • Nombre: Por defecto, toma el nombre de la carpeta raíz del proyecto, no obstante se puede poner el nombre que se desee.
  • Versión: Por defecto es 1.0.0, aun que esto ya es algo mas personal que otra cosa.
  • Descripción: Una descripción corta sobre el propósito de la app.
  • Punto de entrada: Es el archivo que va a buscar npm para iniciar la app. Por defecto es un archivo index.js, aun que, dependiendo de la estructura que se le quiera dar a la app podría tener otro nombre.
  • Comando de Test: Lo explicaremos mas adelante, por ahora simplemente saltearlo.
  • Repositorio de Git: El repositorio donde se va a guardar el proyecto, en mi caso seria este link.
  • Keywords: Son palabras que definen al proyecto. Esta propiedad se usa para cuando lo que estamos desarrollando va a ser un paquete que se va a distribuir por medio de NPM.
  • Autor: El autor o empresa que desarrolla el proyecto.
  • Licencia: En mi caso, va a ser la MIT para todo el código que escriba en este blog.


Al finalizar, la consola nos muestra un extracto de como quedara nuestro archivo package.json.

Si todo esta correcto, escribimos "yes", y de esta forma el proyecto esta iniciado.

Yo particularmente, utilizo Atom como editor de texto, antes usaba Sublime Text, pero siempre voy cambiando, así que esto es a gusto de cada uno.

Lo próximo a realizar, es instalar algunas dependencias que vamos a necesitar para que nuestro flujo de trabajo sea mas rápido. No es el objetivo de esta entrada explicar sobre task-runners como Grunt, Gulp o Webpack, por lo tanto vamos a limitarnos a instalar un reloader automático para que no tengamos que estar recargando manualmente el servidor cada vez que hagamos un cambio en nuestro código, y un transpiler para poder utilizar ES6 en Node.
Estos temas, por si solos, merecen un post especial, el cual ya estoy preparando, pero por lo pronto confíen en mi criterio y apartemos por un momento estos temas del tablero.

El reloader que vamos a utilizar es nodemon.

npm i -D nodemon  

El transpiler es Babel.

npm i -D babel-cli  

El flag -D, le indica a NPM que estas dependencias son solo de desarrollo, y "i" es la forma abreviada de install.

Una vez que ambos paquetes están instalados y listos, en nuestro package.json, vamos a poder ver que han sido agregadas dos dependencias de desarrollo:

"devDependencies": {
    "babel-cli": "^6.22.2",
    "nodemon": "^1.11.0"
  }

Ademas de esto, seguro han notado que se agrego una carpeta /node_modules, en esta es en donde se instalan los paquetes que va utilizando NodeJS.

Todo perfecto, primero entonces vamos a configurar estas librerías de desarrollo.
El archivo package.json tiene una sección dedicada a "scripts".

Para que son estos scripts?

Para todo. Se pueden crear scripts para hacer test, debuguear la app, para cambiar el punto de arranque de la misma, para que ejecute una segunda app cuando la principal inicie, etc.
Para nuestras necesidades, necesitamos crear un script que inicie la app con nodemon y no con npm.

Agregamos entonces, en la sección scripts, uno nuevo como el siguiente:

"start": "nodemon index.js"

Finalmente, nos faltaría un script para que Babel transpile el código ES6 a ES5, para que Node y los navegadores lo entiendan.
El core de Babel, sigue la filosofía de Node de estar muy modularizado, por lo tanto dependiendo de la transformación que necesitemos, deberemos instalar el plugin correspondiente; acá hay una lista de los plugins disponibles.
Existen muchas variaciones de dicho script, pero por ahora solo nos interesa que transpile a un archivo *.js que podamos ejecutar correctamente.

Instalamos el plugin correspondiente a ES6:

npm i -D babel-preset-es2015  

Acto seguido, agregamos un script similar a este:

"build": "babel index.js --out-file index-compiled.js"

Después de esto, queda un paso mas. Debemos crear un archivo .babelrc, en donde definimos los plugins que se van a utilizar, o mejor dicho, las transformaciones que se van a tener que realizar. Por ejemplo, dependiendo de la version de ECMAScript que queremos utilizar o si son archivos *.jsx (REACT).
En el mismo, se debe agregar el siguiente código:

{
    "presets": ["es2015"]
}

Donde dice "es2015", separados de una coma, se tendría que ir agregando los distintos plugins para las transpilaciones necesarias.

Perfecto, todo listo, hora de escribir nuestro Hola Mundo con NodeJS.

Un "Hola Mundo", realmente entendible.

En la definición de nuestro archivo package.json, indicamos que el punto de entrada de nuestra app seria un archivo index.js, por lo tanto, tenemos que crear este archivo en la raíz de nuestro directorio de trabajo.

Dentro del mismo, el siguiente código crea un servidor http y escribe en el DOM un String, que dice "Hola Mundo".

import http from 'http'

http.createServer((req, res) => {  
  res.write('hola Mundo')
  res.end()
}).listen(3000)

Se entiende este código?

Pareciera que se están mezclando cosas acá, no obstante, es muy fácil entender este código.

En la primera linea, se utiliza la nueva sintaxis de ES6 para utilizar el modulo http de Node, este se encuentra en la carpeta "node_modules".

import http from 'http'  

Por este motivo, después del from, se invoca directamente el modulo http y no se escribe su ruta. Cuando se comienza a avanzar en el desarrollo con Node, es común crear nuestros propios módulos, los cuales se importan de la misma manera, pero en este caso, indicando el PATH de dicho modulo dentro de nuestro proyecto.

En la tercer linea, el objeto http llama a su método createServer(), el cual a su vez, llama a otro método listen(), e indica el puerto de escucha de nuestro servidor http, como parámetro de este ultimo. Quizá, de la siguiente manera pueda entenderse mejor:

http.createServer().listen(3000)  

O tambien, podría escribirse así:

http.createServer()  
http.listen(3000)  

En este punto hay que explicar algo, antes de poder seguir. Si están atentos al código, el método createServer(), recibe un parámetro que es otra función.

Una función, como parametro?

En JavaScript, una función puede ser pasada como un parámetro, como si fuera cualquier otro valor. Esto tiene implicaciones muy grandes, ya que si nosotros quisiéramos podríamos crear una función aparte, y pasar a esta como parámetro en el método createServer(), por ejemplo asi:

import http from 'http'

const onRequest = (req, res) => {  
  res.write('Hola Mundo')
  res.end()
}

http.createServer(onRequest).listen(3000)  

Esto quiere decir que, no hace falta ni siquiera definir una función con un nombre, ya que se puede definir directamente como un parámetro de la función principal. Esto, se denomina una función anónima.

Quizá estes pensando cual es el beneficio de utilizar una u otra notación, y la respuesta es que, es indistinto, no obstante, queda mucho mas claro leer el código separando en una función separada, no? ;)

Analizando la función anonima.

La función onRequest(), recibe como parametro dos objetos, req (Request) y res (Response), algo así como solicitud y respuesta. Mas adelante analizaremos estos objetos en detalle, no obstante por ahora solo hace falta saber que el objeto req, es el encargado de darle las peticiones al servidor, y res es el encargado de enviar la respuesta al cliente. Estas "definiciones", deben ser tomadas entre pinzas, ya que son muy a lo campo.

const onRequest = (req, res) => {  
  res.write('Hola Mundo')
  res.end()
}

Esta función, es definida como una constante, por lo tanto, su valor no va a poder ser cambiado durante la ejecución del programa. Este hecho, se va a entender a futuro, cuando avancemos en el uso de ES6.

Entonces, utilizando la sintaxis de las funciones flecha de ES6, definimos esta función anónima, la cual recibe dos objetos (req y res). Analicemos que es lo que pasa adentro de la misma.

Primero, res llama a su método write(), que recibe un parámetro (el cual, podría ser una nueva función anónima), y con el cual , pinta el DOM del explorador con el texto "Hola Mundo". Finalmente, res llama a su método end(), para indicar que la respuesta ha finalizado. Es interesante hacer notar en este punto, que si quisiéramos enviar información de cabecera (la que se puede ver en la consola de Chrome), podríamos utilizar el metodo writeHead() del objeto res, por ejemplo:

res.writeHead(200, { 'Content-type': 'text/html' })  

La información que lleva este método, es el código de estado HTTP 200, que indica que todo funciono bien, y el tipo de contenido que esta sirviendo, en este caso, un texto sobre html. Realmente, podría llevar cualquier información que se considere oportuna, o por ejemplo los web tokens para cuando se esta realizando una app que los utilice.

Finalmente, es una buena practica el agregar mensajes mediante consola. Por ejemplo, vamos agregar un mensaje para cuando el servidor inicia, indicando el puerto de escucha y el host.

console.log('El servidor esta corriendo en el host: localhost, en el puerto: 3000')  

El código final, quedaría similar a este:

import http from 'http'

const onRequest = (req, res) => {  
  res.writeHead(200, { 'Content-Type': 'text/html' })
  res.write('Hola Mundo')
  res.end()
}

http.createServer(onRequest).listen(3000)

console.log('El servidor esta corriendo en el puerto: localhost, en el puerto: 3000')  

Todo listo, estamos en condiciones de ejecutar nuestro flamante primer programa escrito sobre NodeJS.

En la terminal, dentro de nuestro directorio de trabajo, ejecutamos:

npm start  

Con lo cual obtendríamos algo parecido a esto:

Obviamente, no funciono :(

Que paso?

Hay que aprender, si es que deseamos ser buenos desarrolladores, a leer la salida de la consola del compilador, o interprete, de cualquier lenguaje con el que estemos trabajando.
Particularmente, la consola acá nos esta indicando que Node no entiende la orden import, en la primera linea del archivo index.js.

Esto, es algo lógico, ya que import es sintaxis de ES6, y Node aun no la soporta nativamente. Sin embargo, anteriormente, habíamos configurado nuestro proyecto para que pudiera entender dicho código, que paso?

Antes, creamos un script especial llamado /build:

"build": "babel index.js --out-file index-compiled.js"

Pero para que este funcione, hay que ejecutar el comando:

npm run build  

Este, transpilara el archivo index.js, a otro llamado index-compiled.js, el cual, en ES5 se ve asi:

'use strict';

var _http = require('http');

var _http2 = _interopRequireDefault(_http);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var onRequest = function onRequest(req, res) {  
  res.writeHead(200, { 'Content-Type': 'text/html' });
  res.write('Hola Mundo');
  res.end();
};

_http2.default.createServer(onRequest).listen(3000);

console.log('El servidor esta corriendo en el puerto: localhost, en el puerto: 3000');  

Pero, seguramente se habrán percatado de que, hay que realizar una modificación sobre el script "start", ya que el mismo esta apuntando actualmente al archivo index.js, y lógicamente, tendría que ejecutar el archivo index-compiled.js, el cual seria el archivo de producción, mientras que el archivo index.js seria el archivo de desarrollo.
Dicho script, quedaria similar a este:

"start": "nodemon index-compiled.js"

Ahora si, en la consola, ejecutamos la orden:

npm start  

y obtenemos la siguiente salida en la consola:

Ahora, nos dirigimos a un explorador, y vamos a localhost:3000, para encontrar nuestra flamante app NodeJS funcionando.

Todo el código, se puede encontrar en mi cuenta de GitHub, en el siguiente link.

Hasta la próxima entrada! :)

Show Comments

Get the latest posts delivered right to your inbox.