8.7. Node.js
Node.js no es un lenguaje de programación, sino un entorno de ejecución en el lado del servidor (aunque puede funcionar en otros entornos) basado en el lenguaje Javascript.
Por lo tanto, al contrario que el código Javascript convencional, Node.js ejecuta Javascript en el servidor. De este modo, pueden unificarse los lenguajes del lado del cliente y del servidor. Esa es una de las razones que explica el rápido crecimiento que Node.js está experimentando en los últimos años.
Como es lógico, en Node.js se omiten todas las referencias a la API del navegador web y, como es lógico también, se añade soporte para las APIs del sistema operativo u otros subsistemas típicos que se encuentran en los servidores. Por ejemplo, con Node.js tenemos acceso a bases de datos o al sistema de ficheros del servidor.
Vamos a ver una introducción muy breve a Node.js. Ten en cuenta que, además, Node.js no suele usarse de forma independiente para desarrollar aplicaciones web, sino en conjunción con algún framework como Express. De hecho, es rara la aplicación web que se programa sin Express en la actualidad.
8.7.1. Características del lenguaje Node.js
Fecha de aparición: 2009
Perspectivas:
- Uso y popularidad creciente.
- Posibilidad de convertirse en un estándar de desarrollo web fullstack.
- Buena relación señal/ruido.
- Node.js tiene problemas que resolver si quiere imponerse como estándar: la API cambia continuamente y no es compatible hacia atrás, la librería estándar de Javascript es minúscula comparada con otros lenguajes y las librerías de terceros son a veces poco probadas, cambiantes o directamente inexistentes para hacer ciertas cosas que en otras plataformas se dan por supuestas.
Filosofía:
- Usa el motor de JavaScript V8 de Chrome fuera del navegador para ejecutarse en el servidor.
- Las operaciones de E/S son sin bloqueo, esto es, asíncronas.
- El fluje de ejecución no es lineal, sino dirigido por eventos, como ocurre casi siempre en las aplicaciones web. Esto mejora el rendimiento, porque el programa está casi todo el tiempo ocioso, a la espera de que ocurra algún evento.
- Tiene acceso nativo a bases de datos documentales como MongoDB. Hablaremos un poco más sobre qué significa esto más adelante.
- Las aplicaciones altamente escalables.
- Simplicidad y modularidad.
- Gestor npm para librerías de terceros.
8.7.2. Configuración necesaria en el servidor
Node.js incluye su propio servidor web. Recuerda que Node.js no es un lenguaje de programación, sino una plataforma de desarrollo en el lado del servidor completa.
Así que, para lanzar un servidor web con Node.js, lo único que debemos instalar en el servidor es el propio Node.js.
Una vez hecho esto, podemos crear un servidor HTTP básico creando un programa Javascript como este:
var http = require('http');
http.createServer(function (peticion, respuesta)
// Aquí se escribe la respuesta del servidor
).listen(9000, '127.0.0.1'); // Puerto e IP donde escuchará el servidor
Luego bastará con ejecutar este programa en el servidor desde una consola de comandos:
$ node nombre-del-archivo.js
8.7.3. Sintaxis básica de Node.js
El lenguaje de programación que usa Node.js es Javascript, así que hay poco que decir sobre la sintaxis de las estructuras de control, las asignaciones o los operadores, puesto que ya la conoces.
8.7.4. Entrada / Salida con Node.js
La entrada y salida en un entorno Node.js es lo que más se diferencia del Javascript que manejas habitualmente, puesto que ahora estamos trabajando en un servidor, no en un navegador web.
La salida mediante console.log() puede seguir usándose, pero no es lo habitual en aplicaciones web, puesto que esa salida se producirá en la consola de texto del servidor. Para la entrada, existe la función openStdin(), que crea un objeto sobre el que se puede agregar un evento de entrada de datos, como en este ejemplo:
console.log("Escribe tu nombre");
var stdin = process.openStdin();
stdin.addListener("data", function(entradaPorTeclado) {
console.log("Tu nombre es: " + entradaPorTeclado.toString());
});
Aquí se puede ver qué significa que Node.js sea un entorno de ejecución dirigido por eventos: en lugar de lanzar la lectura por teclado y dejar al programa esperando hasta que esa lectura por teclado se produzca, lo que hacemos es asignar un manejador de evento o listener que se ejecutará cuando ocurra cierto evento asociado.
En este ejemplo, asociamos un listener al objeto stdin. Cuando ocurra el evento (en este caso, el evento se llama “data”, que significa “recepción de datos” en el objeto stdin), se ejecutará el código de la función. Mientras tanto, el programa continuará con su ejecución normal, atendiendo a cualquier otro evento que pudiera producirse y que esté programado para atender.
Sin embargo, en una aplicación web, raramente se hace la entrada y salida por consola, ¿verdad? La entrada debería hacerse desde un formulario web, mientras que la salida debería ser una respuesta HTTP.
Te muestro cómo se hace esto con Node.js con otro ejemplo:
var http = require('http');
var datos_del_post;
http.createServer(function(peticion, respuesta) {
if(peticion.method == 'POST'){
var datos_del_post = '';
peticion.on('data', function(trozo_de_datos){
datos_del_post += trozo_de_datos;
});
peticion.on('end', function(){
datos_del_post = querystring.parse(datos_del_post);
respuesta.writeHead(200, {'Content-Type': 'text/html'});
respuesta.write("He recibido correctamente el formulario");
respuesta.write("Estos son los datos que me han llegado:");
respuesta.write("Nombre: " + datos_del_post.name + ". Email = " + datos_del_post.email);
frespuesta.end();
});
}else{
respuesta.writeHead(200, {'Content-Type': 'text/html'});
respuesta.end("No he ha llegado ningún dato por POST");
}
}).listen(9000, '127.0.0.1');
Si observas este código, verás que tiene la misma estructura que el que vimos en el apartado “Configuración necesaria en el servidor”, solo que hemos rellenado la función principal con más cosas.
Esa función tiene dos argumentos, petición y respuesta:
- En petición, el servidor coloca todos los datos de la petición que proviene del cliente. La información del formulario estará aquí, por lo tanto.
- En respuesta, el servidor colocará todos los datos de la respuesta HTTP que va a enviar al cliente.
Fíjate cómo en el código, en primer lugar, miramos si la petición del cliente nos llegó por POST. Si es así, creamos dos manejadores de eventos:
- El manejador “data” se ejecutará cuando llegue un nuevo fragmento de información por POST.
- El manejador “end” se ejecutará cuando haya terminado de recibirse información desde el cliente.
Por eso, en el manejador “data” nos limitamos a ir encadenando los datos que llegan por POST en la variable datos_del_post, y es en el manejador “end” cuando los procesamos. Observa cómo en ese manejador generamos la respuesta HTTP mediante el método write(). La respuesta se envía al cliente cuando se ejecuta el método end().
8.7.5. Módulos de Node.js
Las bibliotecas o módulos, como se llaman en Node.js, son una parte crucial de este entorno de desarollo.
Los módulos, como en cualquier lenguaje, permiten reutilizar código, pero gracias a npm (Node Package Manager), puedes insertar tus librerías dentro de otras, o bien publicarlas para ponerlas al servicio de la comunidad. Y, por supuesto, usar las librerías de otros para tus proyectos.
Require
Para utilizar un módulo, se usa require. Por ejemplo, un módulo de la librería estándar de Node.js se llama “fs” y sirve para manipular ficheros. Si lo necesitamos en nuestro programa, basta con escribir esto:
const fs = require('fs');
fs.readFile('./file.txt', 'utf-8', (err, data) => {
if(err) { throw err; }
console.log('data: ', data);
});
En este ejemplo, hemos importado el módulo “fs” y lo hemos asignado a una variable. A partir de esa variable, podemos usar cualquier método del módulo, como readFile(). La lista de funciones disponibles, como es lógico, tendrás que consultarla en la documentación del módulo.
require buscará los módulos en este orden:
- Módulos de la librería estándar de Node.js
- Módulos importados con npm
- Módulos locales, es decir, que hayamos descargado y almacenado manualmente en el directorio de trabajo.
Librería estándar de Node.js
Con solo instalar Node.js en el sistema, ya dispones de todos los módulos de la librería estándar. Algunos de los módulos más utilizados con aplicaciones web son:
- fs: Para manipular ficheros.
- path: Para trabajar con directorios.
- http: Para crear servidores y clientes.
- url: Para analizar URLs y extraer segmentos y variables de ellas.
Módulos instalados con npm
Los módulos instalados con npm están desarrollados por terceros, es decir, no forman parte del core de Node.js.
Exiten literalmente miles de módulos que puedes instalar con npm, ya que cualquier desarrollador puede publicar el suyo. Asegúrate de consultar la web del desarrollador (y, sobre todo, la página de documentación) antes de instalar un módulo.
Algunos ejmplos de módulos npm que podrían ser útiles en una aplicación web son:
- lodash: Para manipular arrays, objetos, colecciones, cadenas…
- request: Un servidor http más simple de usar que el módulo estándar que viene con Node.js.
- gm: Permite manipular imágenes en el servidor: rotación, recorte, redimensionamiento, etc.
- pdfkit: Para importar y exportar documentos PDF.
- express: Un framework completo (aunque minimalista) para trabajar con Node, con soporte para middlewares, renderizadores de vistas y mucho más.
- restify: Para crear servidores REST de forma aún más minimalista que con express.
Para instalar un módulo mediante npm, basta con teclear esto en un terminal:
$ npm install pdfkit
Después, en nuestro código fuente, podemos usar ese módulo así:
const pdfkit = require('pdfkit');
8.7.6. Node.js y MongoDB
MongoDB es un gestor de bases de datos no relacional (o no-SQL) con una implantación creciente en el mercado profesional.
Se dice que MongoDB está orientado a documentos. Los documentos en estas bases de datos son el equivalente a los registros de las bases de datos relacionales, pero menos rígidos. Un documento puede contener literalmente cualquier cosa, siempre que esté formateada del modo adecuado para la base de datos (lo que significa, generalmente, que esté escrito en correcto XML o JSON).
Las bases de datos documentales son capaces de bucear en esos documentos, crear nuevos documentos, modificarlos y borrarlos, indexarlos y, como es lógico, realizar búsquedas.
MongoDB agrupa los documentos en colecciones, de modo que si un documento es algo parecido a un registro (pero más flexible), una colección es algo parecido a una tabla (también más flexible). No existe el concepto de campo, dominio, ni nada parecido: cada documento de la colección puede tener su propia estructura.
MongoDB y Node.js se han llevado muy bien desde siempre, hasta el punto de que parte del éxito actual de uno se debe al éxito del otro, y viceversa. No es nuestra intención (ni tenemos espacio aquí) adentrarnos en los vericuetos de las bases de datos documentales, sino presentártelas para que conozcas su existencia. Si pretendes dedicarte al desarrollo web profesionalmente, es muy posible que, antes o después, te encuentres con bases de datos no-SQL como MongoDB.
8.7.7. Ejemplo 1 en Node.js: Hola mundo
var http = require('http');
http.createServer(function (req, res)
// Cabecera http. No es imprescindible, pero sí recomendable.
// Recuerda que el código 200 significa "OK" en el protocolo http/https.
// El 'Content-type' más habitual es 'text/html' o 'application/json'.
res.writeHead(200, 'Content-Type': 'text/html');
res.write('Hola, mundo!');
res.end();
).listen(9000); // Puerto donde el servidor escuchará
8.7.8. Ejemplo 2 en Node.js: login con comprobación de email por Ajax
Como en las ocasiones anteriores, solo mostramos aquí el script del lado del servidor (login.js). Para revisar el código del lado del cliente, puedes acudir a la sección dedicada al lenguaje Perl, y sustituir la referencia al script login.pl por login.js. El resto del código permanecerá idéntico.
Hay que destacar que, con el uso de Express, la implementación de esta respuesta del servidor se simplificaría bastante. No lo tomes, por lo tanto, como un ejemplo realista, sino como una muestra el aspecto que tiene el código fuente de Node.js.
Observa que, al tratarse de un código dirigido por eventos, no escribimos el algoritmo de arriba a abajo, en el orden en el que se deben ejecutar las cosas, sino que vamos respondiendo a estos eventos:
- Recepción de datos por GET.
- Finalización de la recepción de datos por GET.
- Conexión lista con la base de datos.
Curiosamente, la consulta a la base de datos no lanza un evento cuando está lista, sino que la función manejadora se indica justo a continuación de la consulta. Esta función se ejecutará cuando la consulta finalice y la base de datos devuelva los resultados.
var mysql = require('mysql');
var http = require('http');
var GETdata, dbconnection;
http.createServer(function(peticion, respuesta) {
if(peticion.method == 'GET'){
var GETdata = '';
peticion.on('data', function(data) { // Vamos recogiendo los datos que llegan por GET
GETdata += data;
});
peticion.on('end', function(){ // Ha finalizado la llegada de datos. Preparamos la conexión con la BD.
GETdata = querystring.parse(GETdata);
var dbconnection = mysql.createConnection({host: "localhost", user: "nombre-usuario", password: "contraseña", database: "nombre-base-de-datos"});
});
}
dbconnection.connect(function(err) { // La conexión con la BD ya está lista
if (err) throw err;
// Lanzamos la consulta y procesamos la respuesta
dbconnection.query(`SELECT * FROM users WHERE username = '${GETdata.username}' AND password = '${GETdata.password}'`,
function (err, result, fields) {
if (err) throw err;
respuesta.writeHead(200, {'Content-Type': 'application/json'});
if (result.length == 0) {
respuesta.write(JSON.stringify({"error": "Username or password not valid"}));
}
else {
respuesta.write(JSON.stringify({"success": "Authentication OK", "userid": result[0].id}));
}
respuesta.end();
});
});
}).listen(9000, '127.0.0.1');