Fundamentos de Nodejs creando un API REST

El lenguaje de programación JavaScript fue evolucionando con el pasar de los años a un lenguaje muy potente y totalmente capaz de ser utilizado en operaciones más pesadas que para las que fue diseñada. En el pasado JavaScript solo era usado para validar formularios HTML y otras operaciones pequeñas. Sin embargo, hoy es usando en el desarrollo backend junto con Nodejs para crear APIs Rest y otros servicios que se ejecutan en servidores de todo el mundo.

En este tutorial aprenderemos los fundamentos de Nodejs y crearemos nuestro primer API Rest utilizando solo Nodejs que luego podrá ser consumido desde cualquier navegador.

Índice

  1. Qué es Nodejs
  2. Diferencia entre Nodejs y el entorno de ejecución del navegador
  3. Comenzando a utilizar Nodejs
  4. Usando variables de entorno en Nodejs
  5. Nodemon
  6. Creando un servidor básico
  7. Analizando el URL
  8. Creando un pequeño servidor con Nodejs
  9. El código

1. ¿Qué es Nodejs?

Nodejs es un entorno de ejecucion (Runtime System o Runtime enviroment) de JavaScript construido con el motor v8 de Google que funciona en el nucleo del navegador Google Chrome. Un entorno de ejecución es una capa encima del sistema operativo que ejecuta un software, en este caso los que están escritos con JavaScript. Esta capa decide cómo el software va consumir la memoria, pasar los parámetros, ejecutar el garbage colector o recolector de basura y otras funciones que se necesitan para que el software funcione correctamente.

Una característica del motor V8 es su tecnología Just in time compilation que es una tecnología que compila el código durante la ejecución del mismo para ganar performance en su ejecución. A esto también se le conoce como Compilación dinámica (dynamic compilation). Esto fue creado en respuesta a que originalmente JavaScript fue creado para ser un lenguaje interpretado y su uso se limitaba a validaciones simples de HTML; sin embargo, esto cambio con el tiempo y ahora se le utiliza para trabajos más pesados. Así que JavaScript sigue siendo interpretado pero Nodejs con esta tecnología identifica las partes recurrentes del código y lo compila en memoria para utilizarlos de forma más rápida cuando son requeridas.

2. Diferencia entre Nodejs y el entorno de ejecución del navegador

Es importante saber que NodeJs no es lo mismo que JavaScript. JavaScript es el lenguaje como tal mientras que Nodejs es la plataforma donde se puede correr código JavaScript fuera del navegador. Su uso masivo hoy en día es en servidores para mantener backends funcionando. Sin embargo, podemos hacer una diferencia entre Nodejs y el entorno de ejcución JavaScript del navegador.

La primera diferencia es que en el entorno de ejecución del navegador tenemos al DOM que es la interface que nos permite manipular el documento HTML desde JavaScript. También tenemos al CSSOM que es la interface que nos permite manipular el CSS desde el código JavaScript. Estos dos elementos no existen en Nodejs, pero por otro lado En Nodejs tenemos diversos módulos que nos permiten comunicarnos con el sistema operativo para crear servidores, utilizar archivos, hacer uso de la memoria y demás utilidades.

El concepto de modulo es muy importante en Nodejs ya que nuestro software estará dividido en módulos y también utilizaremos módulos a modo de librerías para realizar ciertas funciones que necesitamos para nuestro proyecto. Además como ya lo mencionamos Nodejs nos brinda módulos para realizar funciones en nuestro sistema operativo, ya que crear un servidor es utilizar las herramientas del sistema operativo para tal objetivo.

3. Comenzando a utilizar Nodejs

Podemos comenzar a utilizar Nodejs desde la terminal, para esto debemos tener instalado NodeJs en nuestra computadora. Para instalar Nodejs nos debemos dirigir a la página oficial de nodejs.org y luego descargar el instalador para nuestro sistema operativo. La instalación es relativamente sencilla y cuando la hayamos terminado podemos verificar la instalación utilizando el siguiente comando en la terminal.

node -v

Ahora ya podemos comenzar a escribir código JavaScript y ejecutarlo utilizando Nodejs. Para esto crearemos un archivo llamado app.js con un simple console.log("AndyGeek") en su interior y utilizaremos el siguiente comando para ejecutarlo desde la terminal.

node app.js

Ahora probaremos un ejemplo un poco más complicado y veremos de primera mano los problemas que puede traer ser un lenguaje interpretado y no compilado. En este ejemplo mostramos un número en intervalos regulares de tiempo (cada segundo) de tal manera que provoquemos un error en el quinto intervalo de tiempo.

// Se ejecutará cada 1 segundo
let i = 0;
setInterval(() => {
  console.log(i);
  i++;
  if (i === 5) {
    let a = 3 + z;
  }
}, 1000);

Este código comienza a ejecutarse sin problemas porque Nodejs no compila el código, por lo que al inicio no obtenemos ningún error. Pero en plena ejecución Nodejs encuentra un error en el quinto intervalo, por usar una variable no creada, es por eso que nuestra aplicación se detiene ahí y termina de ejecutarse.

Esto puede ser un problema para aplicaciones complejas y de gran tamaño, y para evitarlos se crearon los linters como ESLint que son utilidades que revisan nuestro código antes de ejecutarlos. De esta manera podemos evitar algunos fallos de sintaxis como no declarar una determina variable.

4. Usando variables de entorno en Nodejs

Las variables de entorno son las variables que almacenamos dentro del servidor o el entorno donde estamos ejecutando un determinado código. Esto se realiza porque para cierto tipo de constantes como API keys, contraseñas u otras variables que no deben subirse al repositorio. Así que es más seguro guardarlas en el servidor y evitar que cualquier desarrollador tenga acceso a ellas o podemos declararlas en la terminal al momento de ejecutar nuestro servidor.

Para usar una variable de entorno en Nodejs solo debemos utilizar process.env seguido del nombre de la variable, que por convención se coloca en letras mayúsculas. Veamos el siguiente ejemplo.

let name = process.env.NAME;

console.log("Hola " + name);

Para enviar estas variables de entorno tenemos que declararlas al momento de ejecutar nuestra aplicación de la siguiente manera.

NAME="AndyGeek" node app.js

También podemos definir el valor de la variable de entorno que tomará por defecto cuando no le enviemos esta variable. Para esto procedemos de la siguiente manera.

let name = process.env.NAME || "Sin nombre";
console.log("Hola " + name);

5. Nodemon

Muchas veces necesitamos un entorno de ejecución que ejecute nuestro código cada vez que guardamos el proyecto. De esta forma podemos ver los cambios de manera inmediata sin perder tiempo en volver a utilizar la terminal para ejecutar nuestro proyecto. Para esto existe una herramienta llamada Nodemon.

Para poder usar Nodemon en nuestro proyecto primero debemos instalarlo de manera global usando el siguiente comando.

npm install -g nodemon

Luego de tenerlo instalado en nuestro sistema, podemos ejecutar nuestro código de Nodejs usando el comando nodemon de la siguiente forma.

nodemon app.js

Como podremos ver en la terminal, nuestro proyecto se ejecutará de manera permanente y si guardamos algún cambio en el código podremos ver su ejecución sin necesidad de escribir nuevamente algún comando en la terminal.

6. Creando un servidor básico

Para crear un servidor básico en Nodejs debemos usar el módulo http que nos permitirá responder peticiones Http en nuestro proyecto, tal cual lo hace un servidor. Como dijimos anteriormente en Nodejs trabajaremos a base a módulos, en esta ocasión utilizamos el modulo para realizar operaciones con e protocolo http.

Para crear nuestro servidor utilizaremos el siguiente código que paso a explicar a continuación.

// Obtenemos el modulo http
const http = require("http");

// Con el modulo http creamos un servidor que ejecuta un callback
const server = http.createServer(function (req, res) {
  // req es el require o peticion que te hace el cliente
  // req.url es una propiedad de la petición que nos muestra el url
  console.log(req.url);

  // res es el response o respuesta que hace el servidor
  // res.end() termina el proceso de respuestas por lo que el navegador deja de cargar
  res.end();
  // Despues de usar este método ya no se puede responder nada más al cliente
});

// Le indicamos a nuestro servidor que escuche el puerto 3000
server.listen(3000);

// Mostramos un mensaje que indica que comenzamos a escuchar con nuestro servidor
console.log("Escuchando en el puerto 3000");

Ahora ejecutamos esta pieza de código con nodemon y veremos que es que ya tenemos un servidor escuchando en el puerto 3000 del localhost. Además nuestro servidor nos muestra en la terminal a qué dirección se visitó desde el navegador.

Podemos mejorar el servidor enviando una respuesta al navegador. Para esto utilizamos el siguiente método dentro del callback del servidor. Este método nos permite escribir un mensaje como response o respuesta.

res.write("Hola ya se usar http de nodejs");

También podríamos escribir una cabecera a la respuesta usando el siguiente método. Sin embargo, esto solo se podrá ver en la utilidad de desarrollador de Google Chrome o usando alguna herramienta como Postman.

res.writeHead(201, { "Content-type": "text/plain" });

Pero si lo que queremos es dar respuesta a múltiples rutas, podemos utilizar el siguiente código donde ordenamos las respuestas utilizando una estructura switch de la siguiente forma.

const http = require("http");

const server = http.createServer(function (req, res) {
  console.log("Nueva peticion");
  switch (req.url) {
    case "/hola":
      res.write("Hola AndyGeek");
      break;
    case "/andy":
      res.write("Hola Andy");
      break;
    default:
      res.write("Error 404");
      break;
  }
  res.end();
});
server.listen(3000);

console.log("Escuchando en el puerto 3000");

Y aún podemos seguir mejorando el código de tal forma que podamos utilizar un puerto especifico o una Ip específica, veamos como.

const http = require("http");
const hostname = "127.0.0.1";
const port = process.env.PORT || 3000;

const server = http.createServer(function (req, res) {
  console.log("Nueva peticion");
  switch (req.url) {
    case "/hola":
      res.write("Hola wey");
      break;
    case "/andy":
      res.write("Hola Andy");
      break;
    default:
      res.write("Error 404");
      break;
  }
  res.end();
});

server.listen(port, hostname, function () {
  console.log(`Server running at http://${hostname}:${port}/`);
});
# Para ejecutar el código usamos
PORT=8080 node app.js
Podemos escoger toda la serie de host 127.0.0.1 ya que representan al localhost, además podemos hacer uso de otros host o ips pero no deberan ser host de servicios publicos de internet. Incluso también podemos optar por obviar el parametro hostname si solo usaremos el localhost.

7. Analizando el URL

En Nodejs también tenemos un modulo para analizar los urls que recibe el servidor. En el camino a crear nuestro propio API REST utilizaremos este modulo y las propiedades del require para analizar las peticiones que realiza el cliente y responder lo que queremos responder. Los frameworks como Express ya traen funciones que hacen estas validaciones por nosotros. Por ejemplo, Express tiene métodos para responder a una petición POST de una ruta determinada, pero nosotros crearemos estas validaciones desde cero.

En el siguiente ejemplo de código creamos un servidor que analiza las rutas de las peticiones del cliente y los limpia para mostrarlos en la consola del servidor.

const http = require("http");
const url = require("url");
const port = 3000;

var server = http.createServer(function (req, res) {
  // url.parse(req.url) nos permite obtener un objeto con las propiedades del url.
  var parsedUrl = url.parse(req.url);
  // pathname nos permite obtener la ruta del url
  var path = parsedUrl.pathname;
  // Luego utilizamos el método replace para reemplazar el / en el resultado
  var trimmedPath = path.replace(/^\/+|\/+$/g, "");
  // Utilizando el método end también podemos mostrar un mensaje en pantalla
  res.end("Hello world\n");
  // Obtenemos en la consola el resultado del analisis del url
  console.log("request: " + trimmedPath);
});

server.listen(port, function () {
  console.log(`Server running at http://localhost:${port}/`);
});
De seguro miras varios request con favicon.ico, eso es porque este request es automatico. El navegador hace ese request automaticamente para colocar un icono de la página en el navegador. Podemos evitar esto, por ahora, usando la herramienta curl que nos permite hacer peticiones hacia un determinado host.

Como ves analizar el url y mostrar algo en la pantalla del navegador no es complejo. Y recuerda que aún no estamos utilizando ningún framework y solo estamos haciendo uso de Nodejs puro.

Analizando el tipo de petición

Ahora analizaremos la petición y mostraremos que tipo de petición realizó el cliente a nuestro servidor. Para hacer esto solo debemos usar la propiedad method como en el siguiente ejemplo.

var method = req.method;

Podemos probar esto con cada uno de los métodos de peticion usando la herramienta Postman. Además podemos hacer una verificación el método para responder unicamnte cuando se realice peticiones POST, por ejemplo.

const http = require("http");
const url = require("url");

const port = 3000;

const server = http.createServer(function (req, res) {
  if (req.method === "POST" && req.url == "/hola") {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end();
  } else {
    res.statusCode = 404;
    res.end();
  }
});

server.listen(3000);

Analizando los parámetros de la petición

Otra cosa que también podemos hacer es analizar la cadena de consulta o queryString de nuestra web. Así nosotros podemos obtener los parametros que nos envíen a través de una ruta. Para esto solo debemos utilizar el siguiente código.

var queryString = parsedUrl.query;

Analizando el header de la petición

También podemos obtener los headers de la petición en formato de objeto de la siguiente manera.

var headers = req.headers;

Analizando el cuerpo de la petición

Pero lo que quizás estemos más interesados de recibir es el cuerpo o body de la petición, esto se hace mediante el payload. El payload contiene los fragmentos de datos que pasamos al servidor que no se pueden ver a simple vista como con los parametros que los enviamos por la ruta. Dentro del payload están los datos que se envían en el body de nuestra petición, por ejemplo el JSON de una petición POST. Veamos como ver el payload de una petición en Nodejs.

Primero debemos saber que el método req.on() enlaza un evento con una función callback. En este caso estamos enlazando el evento data con un callback. Lo que sucede es que los datos se envían en fragmentos (chunks) que son emitidos junto al evento data. Lo que hacemos aquí es recolectar cada uno de esos fragmentos en la variable buffer que debe ser declarada previamente.

const StringDecoder = require('string_decoder').StringDecoder;

...
var decoder = new StringDecoder('utf-8');
let buffer = "";
req.on('data', function(chunk) {
    buffer += decoder.write(chunk)
});

Sin embargo, esto no es suficiente. Para poder mostrar los datos necesitamos finalizar la recolección de datos con el evento end, para lo cual utilizamos el siguiente código.

req.on("end", function () {
  buffer += decoder.end();
  console.log(buffer);
  res.end();
});

Al finalizar debemos tener el siguiente código.

const http = require("http");
const url = require("url");
const StringDecoder = require("string_decoder").StringDecoder;
const port = 3000;

var server = http.createServer(function (req, res) {
  var parsedUrl = url.parse(req.url);
  var decoder = new StringDecoder("utf-8");
  var buffer = "";
  req.on("data", function (data) {
    buffer += decoder.write(data);
  });
  req.on("end", function () {
    buffer += decoder.end();
    console.log(buffer);
  });
  res.end();
});

server.listen(port, function () {
  console.log(`Server running at http://localhost:${port}/`);
});

Otro método para realizar lo mismo es el siguiente. En este ejemplo almaceno los datos en un arreglo y utilizo Buffer.concat() para unir objetos Buffer en un arreglo. Un Buffer es una secuencia fija de bytes que es la forma en como se envian los datos al servidor.

const http = require("http");
const port = 3000;

const server = http.createServer(function (req, res) {
  let body = [];
  req.on("data", (chunk) => {
    body.push(chunk);
  });
  req.on("end", () => {
    res.writeHead(200, { "Content-Type": "text/plain" });
    body = Buffer.concat(body).toString();
    res.end(body);
  });
});

server.listen(3000);

8. Creando un pequeño servidor con Nodejs

Con todo lo que aprendimos hasta ahora ya podemos crear un pequeño servidor más completo, que nos muestre información de la petición y además que responda en caso de que la petición sea la correcta.

const http = require("http");
const url = require("url");

const server = http.createServer(function (req, res) {
  // Para obtener la ruta
  var parsedUrl = url.parse(req.url);
  var path = parsedUrl.pathname;
  var trimmedPath = path.replace(/^\/+|\/+$/g, "");

  // Para obtener la cabecera
  var headers = req.headers;

  // Para obtener el método de la petición
  var method = req.method;
  // Para obtener los parámetros de la petición
  var queryString = parsedUrl.query;

  // Para obtener el cuerpo de la petición
  let body = [];

  req.on("data", function (chunk) {
    body.push(chunk);
  });

  req.on("end", function () {
    body = Buffer.concat(body).toString();

    // Definimos la variable que nos devolverá el método asociada a la ruta que ingresemos
    var chosenHandler =
      typeof router[trimmedPath] !== "undefined"
        ? router[trimmedPath]
        : handlers.notFound;

    // Este objeto almacenará los datos de la petición
    var data = {
      trimmedPath: trimmedPath,
      method: method,
      headers: headers,
      queryStringObject: queryString,
      payload: body,
    };

    // Este método administra la petición y en base al callback que tiene asociado
    // ejecuta lo necesario como la respuesta que dará o la que se mostrará en la consola
    chosenHandler(data, function (statusCode, payload) {
      statusCodde = typeof statusCode == "number" ? statusCode : {};
      var payloadString = JSON.stringify(payload);
      // Convertirá nuestro response en un objeto Json
      res.setHeader("Content-Type", "application/json");
      res.writeHead(statusCode);
      res.end(payloadString);
      console.log("Returning the response of ", statusCode, payloadString);
    });
  });
});

server.listen(3000, function () {
  console.log("Server runign");
});

// Definimos el objeto handlers que contendrá los manejadores de cada ruta
var handlers = {};

handlers.sample = function (data, callback) {
  callback(406, { name: "sample handler" });
};

handlers.notFound = function (data, callback) {
  callback(404);
};

// Definimos las rutas y el método asociadas a ellas
var router = {
  sample: handlers.sample,
};

Ahora si queremos crear algun otro endpoint en nuestro servidor solo debemos agregar el handler del endpoint y agregarlo en el objeto de rutas. Veamos el siguiente ejemplo donde agregamos el endpoint ping a nuestro servidor.

handlers.ping = function (data, callback) {
  callback(200);
};

var router = {
  ping: handlers.ping,
  sample: handlers.sample,
};

Además podemos seguir agregando más cosas a nuestro servidor como por jemplo un archivo de configuración que nos permitirá definir la configuración inicial para lanzar nuestro servidor mediante las variables de entorno. Asi que podemos crear un archivo config.js y colocar el siguiente código que reconoce las variables de entorno y lo exporta hacia nuestro archivo principal de nuestro servidor.

var enviroments = {};

enviroments.staging = {
  port: 3000,
  envName: "develop",
};

enviroments.production = {
  port: 5000,
  envName: "production",
};

var currentEnviroment =
  typeof process.env.NODE_ENV == "string"
    ? process.env.NODE_ENV.toLowerCase()
    : "";

var enviromentToExport =
  typeof enviroments[currentEnviroment] == "object"
    ? enviroments[currentEnviroment]
    : enviroments.develop;

module.exports = enviromentToExport;

En el archivo donde tenemos nuestro servidor App.js utilizamos el siguiente código para importar el archivo de configuración.

var config = require("./config")

Además, en este mismo rchivo, debemos utilizar config.port en lugar de colocar 3000 directamente. De esta forma estamos llamando la configuración del puerto desde el archivo config.js.

Listo!! Ya tenemos un pequeño servidor en nuestro proyecto. Este servidor por ahora solo tiene dos endpoint ping y sample. En siguientes tutoriales iremos mejorando este servidor hasta convertirlo en un gran API REST utilizando solo JavaScript y Nodejs.

9. El código

El código de este proyecto está en el siguiente repositorio github.com/andygeek/server-only-nodejs. Te sugiero intentarlo tu mismo antes de ver el código.