Logo Studenta

Crear un API con NODE JS

¡Este material tiene más páginas!

Vista previa del material en texto

1.1
1.2
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
3.1
3.2
3.3
4.1
4.2
4.3
4.4
Tabla de contenido
Portada
Introducción
¿Que es un API?
Configurando el proyecto con Express JS
Inicializando el proyecto
Herramientas de desarrollo
Crear un simple Web Server
Utilizando Express JS
Configuración y variables de entorno
Middleware para manejo de errores
Sistema de Logs
Postman
Router y Routes en Express JS
Utilizando el Router y Routes de Express
Creando el layout del API
Capturando y procesando parámetros de las peticiones
Patrones asincrónicos de JavaScript
Patrones asincrónicos de JavaScript
Callbacks
Promises
async / await
Persistencia de datos
2
5.1
5.2
5.3
5.4
5.5
5.6
5.7
5.8
5.9
5.10
5.11
5.12
5.13
6.1
6.2
6.3
6.4
6.5
6.6
6.7
7.1
7.2
7.3
7.4
8.1
Bases de datos SQL y NoSQL
Instalando y configurando MongoDB
Conectando con MongoDB
Mongoose Models
Procesando parámetros comunes con middleware
Estandarización de la respuesta
Mongoose Schemas
Paginación
Ordenamiento
Creando recursos
Relaciones entre recursos
Consultar recursos anidados
Añadir recursos anidados
Asegurando el API
Añadir y remover campos de un documento
Administración de usuarios
Autenticación de usuarios
Autorización de usuarios
Validaciones personalizadas
Limpieza de campos
Restricción de las peticiones
Probando el API
Pruebas
Configurando pruebas
Pruebas unitarias
Pruebas de integración
Documentando el API
Configurando Swagger y JSDoc
3
8.2
8.3
Documentando las rutas y creando modelos
Conclusión
4
Introducción
A quién está dirigido este libro
Este libro va dirigido a programadores que han iniciado con Node.js o tienen experiencia en
otros lenguajes de Backend como Java, Rails o PHP y quieren utilizar Node.js como
componente de Middleware o Backend para hacer un REST API Web.
Si eres nuevo en Javascript se recomienda tomar el siguiente curso:
Introduction to JavaScript - Code Academy
Adicionalmente en este libro se asume conocimientos de los features básicos de ES2015 al
menos:
Arrow functions
let + const
Template Strings
Destructuring assignment
Promises
Para mayor información puede consultar el siguiente enlace:
Learn ES2015 - Babel
Si eres nuevo en Node.js se recomienda leer el siguiente libro de este misma serie:
Introducción a Node JS
También se asume que el lector tiene conocimientos básicos de comandos en la Terminal y
utilización básica de git.
Acerca de este libro
Este libro tiene un objetivo teórico-práctico, durante el transcurso de este construiremos una
aplicación desde el inicio hasta el fin, comenzaremos por lo conceptos básicos y
gradualmente en cada capítulo introduciremos nuevos conceptos, fragmentos de código y
buenas prácticas, veremos como la aplicación se convierta en un proyecto robusto. Pero a
su vez si usted lo desea puede saltar a un capitulo en especifico, por ejemplo para ver el
versionamiento de API o la persistencia de datos con MongoDB.
Introducción
5
https://www.codecademy.com/learn/introduction-to-javascript
https://babeljs.io/learn-es2015/
https://www.gitbook.com/book/gmoralesc/introduccion-a-node-js/details
Utilizaremos git como software de control de versiones para organizar cada uno de los
pasos incrementales (features) de la aplicación, brindando así la posibilidad de saltar a un
paso específico en el tiempo de desarrollo del proyecto y si usted lo desea comenzar desde
allí, a la vez nos brinda la ventaja de ver los cambios en los archivos de manera visual y así
poder comprobar en caso de una omisión o error.
Acerca del autor
Soy un entusiasta de la Web desde la década de finales de los 90 cuando se utilizaba el
bloc de notas y nombre de etiquetas en mayúsculas para hacer las páginas Web, soy
Ingeniero de Sistemas y tengo más de diez años de experiencia como profesor de pregrado
en la asignatura de programación bajo la Web y en posgrado con otras asignaturas
relacionadas con el mundo de desarrollo de software, trabajo actualmente en la industria del
desarrollo de software específicamente la programación Web. Puedes encontrar más
información acerca de mi hoja de vida en el siguiente enlace: http://gmoralesc.me.
Gustavo Morales
Agradecimientos
Quiero agradecer a mi esposa Milagro y mi hijo Miguel Angel por apoyarme y entender la
gran cantidad de horas invertidas frente al computador: investigando, estudiando, probando
y escribiendo.
Introducción
6
http://gmoralesc.me
¿Que es un API?
Es un acrónimo en inglés de Application Programming Interface, lo cual en otras palabras
un pocos menos abstractas, es la interfaz que una aplicación expone para que otras
aplicaciones interactúen con sus métodos o servicios, a través de una interfaz
consumiendo unas recursos específicos.
API Web
En nuestro proyecto crearemos una API Web la cual utiliza como interfaz el protocolo Web
es cuál es HTTP (o HTTPS sea el caso) y los recursos son las entidades, por ejemplo
nosotros vamos a crear un administrador de tareas por lo tanto nuestros recursos serán
tareas, usuarios y grupos, entonces nuestras entidades serán: tasks, users y groups,
normalmente las entidades se nombran el plural y las cuales se acceden a través de unas
rutas en este caso son urls.
Se recomienda utilizar el idioma inglés independientemente de la lengua nativa de la
aplicación.
Existen otros tipos de API por ejemplo en vez del protocolo HTTP pueden ser sockets y en
vez de entidades pueden ser canales, este tipo de API son muy utilizadas en juegos en
línea, por lo tanto es importante definir que tipo de API estamos construyendo.
Códigos y verbos HTTP
El protocolo HTTP utiliza diferentes verbos para las diferentes operaciones que se realizan
y dependiendo el resultado de estas operaciones vienen con diferentes códigos.
Los verbos más utilizados por defecto en la Web son:
GET: Se utiliza cuando se envía una solicitud para obtener un recurso por ejemplo un
documento HTML a través de una url.
OPTIONS: Se utiliza como petición auxiliar para comprobar si el servidor soporta el
método que se va a utilizar en general cualquiera diferente a GET, esta comprobación
se llama preflight.
POST: Se utiliza para crear un recurso, normalmente enviar información de formularios
Web.
Pero no son los únicos, existen además: PUT, PATCH y DELETE.
¿Que es un API?
7
Dentro del listado de códigos tenemos:
2xx: Este grupo se utiliza para operaciones exitosas.
200: Cuando la operación es exitosa.
201: Lo mismo que el 200 pero en este caso cuando además se crea un recurso
de manera exitosa.
204: Lo mismo que el 201 pero esta vez no fue necesario enviar ningún tipo de
información de vuelta
3xx: Este grupo se utiliza para indicar redirecciones.
4xx: Este grupo se utiliza para enumerar los errores del lado del cliente.
400: Indica que la petición no fue bien realiza normalmente cuando los datos se
envian mal formados.
401: No está autorizado para acceder al recurso, normalmente se utiliza cuando no
se está autenticado.
403: Puede que esté "autenticado" pero no tiene permisos para acceder a ese
recurso
404: Recurso no encontrado
422: Indica que la petición no se puede procesar debido a que faltan datos
requeridos o están en un formato incorrecto.
5xx: Este conjunto asociado a errores del lado del servidor.
REST API Web
REST es un acrónimo de Representational State Transfer, que especifica un contrato por
defecto para la relación que debe haber entre las rutas, verbos, códigos (estos dos
anteriores pertenecientes al protocolo HTTP) y operaciones principales como: Crear
(Create), Leer (Read), Actualizar (Update) y Borrar (Delete) conocido mucho por su
acrónimo CRUD, la siguiente tabla mostrará cómo sería para nuestra entidad tasks:
Verbo StatusCode Operación Descripción
/api/tasks/ POST 201 CREATE Crear un tarea
/api/tasks/ GET 200 READ Leer todos las tareas
/api/tasks/:id GET 200 READ Leer una tarea
/api/tasks/:id PUT /PATCH 200 UPDATE
Actualizar la información de
una tarea
/api/tasks/:id DELETE 200 (o204) DELETE Eliminar una tarea
¿Que es un API?
8
Estecontrato no es una camisa de fuerza, pero se deben respetar las definiciones básicas,
no está permitido por ejemplo crear una tarea con el método DELETE en vez de POST, no
tiene mucho sentido, pero se pueden añadir más muchas más operaciones, recursos
anidados y demás, se dice que un API es RESTful cuando cumple con este contrato
mínimo.
¿Que es un API?
9
Inicialización del proyecto
Seleccionamos un directorio de trabajo y creamos el directorio de la aplicación:
mkdir checklist-api && cd checklist-api
Inicializamos el proyecto de Node JS:
npm init -y
El comando anterior genera el archivo package.json que luego se puede editar para
cambiar los valores de la descripción, autor y demás.
Inicializamos el repositorio de git:
git init
Creamos el archivo .gitignore en la raíz del proyecto:
touch .gitignore
Con el siguiente contenido y lo guardamos:
node_modules/
.DS_Store
Thumbs.db
Creamos el archivo principal de la aplicación:
touch index.js
Agregamos el script inicial en el archivo package.json para ejecutar nuestro archivo
principal:
"scripts": {
 "start": "node index",
 "test": "echo \"Error: no test specified\" && exit 1"
},
Inicializando el proyecto
10
Para terminar con la configuración de la aplicación hacemos el commit inicial:
git add --all
git commit -m "Initial commit"
Inicializando el proyecto
11
Herramientas de desarrollo
Nodemon
Es muy tedioso cada vez que realizamos un cambio en los archivos tener que detener e
iniciar nuevamente la aplicación, para esto vamos a utilizar una librería llamada nodemon,
esta vez como una dependencia solo para desarrollo, ya que no la necesitamos cuando la
aplicación esté corriendo en producción, esta librería nos brinda la opción de que después
de guardar cualquier archivo en el directorio de trabajo, nuestra aplicación se vuelva a
reiniciar automáticamente.
npm install -D nodemon
Luego modificamos la sección de scripts del archivo package.json para añadir el script
que ejecutará nuestra aplicación en modo de desarrollo y en el script start en modo de
producción, de la siguiente manera:
"scripts": {
 "dev": "nodemon",
 "start": "node index",
 "test": "echo \"Error: no test specified\" && exit 1"
},
No es necesario indicarle a nodemon que archivo va a ejecutar pues por convención
buscará el archivo index.js , el cual es el punto de entrada de nuestra aplicación. Mas
adelante se configurará el script de pruebas: test 
Si el servidor se está ejecutando lo detenemos con CTRL+C , pero esta vez lo ejecutamos
con npm run dev .
Para comprobar que todo esté funcionando, abrimos el navegador en la siguiente dirección
 http://localhost:3000 .
Antes de finalizar hacemos commit de nuestro trabajo:
git add --all
git commit -m "Add Nodemon for development"
Más información:
Herramientas de desarrollo
12
Nodemon
ESLint
Es recomendado añadir una herramienta que nos compruebe la sintaxis de los archivos
para evitar posibles errores y porque no, normalizar la forma en que escribimos el código,
para ello utilizaremos una librería llamada ESLint que junto con el editor de texto
comprobará la sintaxis de los archivos en tiempo real:
npm install -D eslint
ESLint nos permite seleccionar una guia de estilo ya existente o también poder crear
nuestra propia guia, todo depende del común acuerdo del equipo de desarrollo.
Inicializamos la configuración con el siguiente comando:
./node_modules/.bin/eslint --init
Alternativamente con la utilidad npx , instalada globalmente por Node.js, con el siguiente
comando:
npx eslint --init
Si ESLint está instalado de manera global, el comando anterior se podría reemplazar
con: eslint --init 
Contestamos las preguntas del asistente de configuración de la siguiente manera:
? How would you like to use ESLint? 
 To check syntax only 
 To check syntax and find problems 
❯ To check syntax, find problems, and enforce code style
? What type of modules does your project use? 
 JavaScript modules (import/export) 
❯ CommonJS (require/exports) 
 None of these
Herramientas de desarrollo
13
https://nodemon.io/
? Which framework does your project use? 
 React 
 Vue.js 
❯ None of these
? Where does your code run? 
 ◯ Browser
❯◉ Node
? How would you like to define a style for your project? 
❯ Use a popular style guide 
 Answer questions about your style 
 Inspect your JavaScript file(s)
? Which style guide do you want to follow? 
❯ Airbnb (https://github.com/airbnb/javascript) 
 Standard (https://github.com/standard/standard) 
 Google (https://github.com/google/eslint-config-google)
? What format do you want your config file to be in? 
 JavaScript 
 YAML 
❯ JSON
Checking peerDependencies of eslint-config-airbnb-base@latest
The config that you've selected requires the following dependencies:
eslint-config-airbnb-base@latest eslint@^4.19.1 || ^5.3.0 eslint-plugin-import@^2.14.0
? Would you like to install them now with npm? (Y/n) Y
Una vez finalizado el proceso creará un archivo en la raíz de nuestro proyecto llamado
 .eslintrc.json , el cual contiene toda la configuración. Adicionalmente vamos a añadir las
siguientes excepciones:
1. Vamos a utilizar el objeto global console para imprimir muchos mensajes en la
consola y no queremos que lo marque como error.
2. A modo de ejemplo más adelante vamos añadir un parámetro que no utilizaremos
inmediatamente pero nos servirá mucho para explicar un concepto el cual es next y no
queremos que no los marque como error.
Herramientas de desarrollo
14
{
 "env": {
 "commonjs": true,
 "es6": true,
 "node": true
 },
 "extends": "airbnb-base",
 "globals": {
 "Atomics": "readonly",
 "SharedArrayBuffer": "readonly"
 },
 "parserOptions": {
 "ecmaVersion": 2018
 },
 "rules": {
 "no-console": "off",
 "no-unused-vars": [
 "error",
 {
 "argsIgnorePattern": "next"
 }
 ]
 }
}
Antes de finalizar hacemos commit de nuestro trabajo
git add --all
git commit -m "Add ESLint for development"
Más información:
ESLint
Visual Studio Code
Para añadir nuestra configuración personalizada para el proyecto primero se debe crear el
directorio y el archivo de configuración:
mkdir .vscode
touch .vscode/settings.json
Dentro del archivo de configuración settings.json colocamos la siguiente información:
Herramientas de desarrollo
15
https://eslint.org/
{
 "editor.formatOnSave": true,
 "editor.renderWhitespace": "all",
 "editor.renderControlCharacters": true,
 "editor.trimAutoWhitespace": true,
 "editor.tabSize": 2,
 "files.insertFinalNewline": true,
 "files.eol": "\n",
 "prettier.trailingComma": "es5",
 "prettier.eslintIntegration": true
}
Si estás utilizando Visual Studio Code puedes añadir estos parámetros en la configuración:
Establecer que siempre se ordene el documento a la hora de guardar el archivo, que
muestre todos los espacios en blanco y caracteres especiales y remueva todos los
espacios en blanco que sobren.
Dejar una línea en blanco al final de cada archivo (requerido por defecto por ESLint) e
indicar cuál será el carácter de fin de línea, esto más que todo para retrocompatibilidad
con los sistemas Windows.
Si tienes el plugin de Prettier para ordenar el documento puedes indicarle que tome las
reglas de la configuración ESLint que tienes en el proyecto.
Finalmente si deseas añadir una coma al final de algunas estructuras de JavaScript
puedes indicarle a prettier que lo haga, esta es una buena práctica cuando se utiliza git
y cada línea nueva no necesita añadir una coma en el elemento anterior.
Es posible establecer unos plugins sugeridos para que una vez el programador abra el
proyecto en Visual Studio Code muestra la sugerencia:
touch .vscode/extensions.json
Con el siguiente contenido:
{
 "recommendations": [
 "waderyan.nodejs-extension-pack",
 "dbaeumer.vscode-eslint",
 "esbenp.prettier-vscode"
 ]
}
Depuración con Visual Studio Code
Crear el archivo de configuración:
Herramientas de desarrollo
16
touch .vscode/launch.json
Con lasiguiente información:
{
 "version": "0.2.0",
 "configurations": [
 {
 "type": "node",
 "request": "attach",
 "name": "Node: Nodemon",
 "processId": "${command:PickProcess}",
 "restart": true,
 "protocol": "inspector"
 }
 ]
}
Luego modificamos la sección de scripts del archivo package.json para añadir la opción
que permitirá hacer debug al nodemon:
"scripts": {
 "dev": "nodemon --inspect",
 "start": "node index",
 "test": "echo \"Error: no test specified\" && exit 1"
},
Se debe detener el Servidor una vez más para tomar los últimos cambios.
Antes de finalizar hacemos commit de nuestro trabajo:
git add --all
git commit -m "Add config for debugging with VSCode"
Más información:
VS Code - Nodemon
Herramientas de desarrollo
17
https://github.com/Microsoft/vscode-recipes/tree/master/nodemon
Creando un simple Web Server
Antes de comenzar el proyecto vamos a crear un servidor Web sencillo, ya que nuestra
aplicación será una REST API Web que se podrá consumir mediante el protocolo HTTP,
será un API para administrar un listado de tareas de cada usuario y que opcionalmente se
pueden organizar en grupos, todo lo anterior con persistencia de datos.
Tomaremos el ejemplo del Web Server que se encuentra en la documentación de Node.js y
reemplazamos el contenido por el siguiente código:
// index.js
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
 res.statusCode = 200;
 res.setHeader('Content-Type', 'text/plain');
 res.end('Hello World');
});
server.listen(port, hostname, () => {
 console.log(`Server running at http://${hostname}:${port}/`);
});
En el ejemplo anterior Node.js utiliza el módulo de http para crear un servidor Web que
escuchará todas las peticiones en la dirección IP y puerto establecidos, como es un ejemplo
responderá a cualquier solicitud con la cadena Hello World en texto plano sin formato,
solo para mostrar que el servidor Web está funcionando accedemos al navegador en la
siguiente dirección:
http://localhost:3000/
Normalmente en un REST API Web se consume mediante el protocolo HTTP y la respuesta
son objetos en formato JSON (el cual introduciremos más adelante), organizado en una
serie de rutas que adicionalmente reciben parámetros, el contrato para establecer la
relación entre las acciones del REST API Web y los verbos HTTP es bastante
estandarizada, pero no la estructura de la respuesta y la organización de la rutas, en esta
aplicación comenzaremos con una estructura sugerida que irá cambiando dependiendo los
diferentes casos y necesidades que se presenten.
Crear un simple Web Server
18
https://nodejs.org/api/synopsis.html
Antes de finalizar hacemos commit de nuestro trabajo:
git add index.js
git commit -m "Create a simple Web server"
Más información:
HTTP
Crear un simple Web Server
19
https://nodejs.org/dist/latest-v6.x/docs/api/http.html
Utilizando Express JS
Express JS es un librería con un alto nivel de popularidad en la comunidad de Node.js e
inclusive esta mismo adoptó Express JS dentro de su ecosistema, lo cual le brinda un
soporte adicional, pero esto no quiere decir que sea la única o la mejor, aunque existen
muchas otras librerías y frameworks para crear REST API, Express JS si es la más sencilla
y poderosa, según la definición en la página Web de Express JS: "Con miles de métodos de
programa de utilidad HTTP y middleware a su disposición, la creación de una API sólida es
rápida y sencilla."
Añadiendo Express JS
Instalamos el módulo de express como una dependencia de nuestra aplicación:
npm install -S express
Esto añadirá una nueva entrada en nuestro archivo package.json en la sección de
 dependencies indicando la versión instalada.
Creamos un directorio llamado server donde se almacenarán todos los archivos
relacionados con el servidor Web así como su nombre lo indica:
mkdir server
touch server/index.js
Tomaremos como ventaja la convención de Node.js en la nomenclatura de los módulos
de la raíz de cada carpeta llamándolos index.js
Tomaremos el ejemplo de Hello World que se encuentra en la documentación de Express
JS y reemplazamos el contenido del archivo por el siguiente código:
Utilizando Express JS
20
https://nodejs.org/en/blog/announcements/foundation-express-news/
http://expressjs.com/
https://expressjs.com/en/starter/hello-world.html
// server/index.js
const express = require('express');
const port = 3000;
const app = express();
app.get('/', (req, res) => {
 res.send('Hello World!');
});
app.listen(port, () => {
 console.log(`Example app listening on port ${port}!`);
});
Como lo vemos es muy similar al anterior pero con una sutiles diferencias:
Utilizamos la librería de express en vez del módulo http .
Creamos una aplicación de express (inclusive se pueden crear varias) y la
almacenamos en la variable app .
Esta vez solo aceptaremos peticiones en la raíz del proyecto con el verbo GET de
HTTP.
Si el servidor se está ejecutando lo detenemos con CTRL+C , lo ejecutamos nuevamente con
el comando node server/index y abrimos el navegador en la siguiente dirección
 http://localhost:3000/ . Luego volvemos a ejecutar la aplicación en modo de desarrollo.
Aparentemente vemos el mismo resultado, pero express nos ha brindado un poco más de
simplicidad en el código y ha incorporado una gran cantidad de funcionalidades, las cuales
veremos más adelante.
Organizando la aplicación
Antes de continuar vamos a organizar nuestra aplicación, crearemos diferentes módulos
cada uno con su propósito específico, ya que esto permite separar la responsabilidad de
cada uno de ellos, organizarlos en directorios para agrupar los diferentes módulos con
funcionalidad común, así nuestro archivo index.js será más ligero, legible y estructurado,
ya que NO es una buena práctica colocar todo el código en un solo archivo que sería difícil
de mantener, esto se le conoce como aplicación monolítica.
Empezamos organizando la aplicación de Express JS:
Utilizando Express JS
21
// server/index.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
 res.send('Hello World');
});
module.exports = app;
Como podemos observar ahora nuestra aplicación de express es un módulo que tiene una
sola responsabilidad de crear nuestra aplicación y es independiente a la librería que
utilizamos.
Creamos el directorio y módulo básico de configuración:
mkdir -p server/config
touch server/config/index.js
En el archivo de configuración es donde centralizamos todas las configuraciones de la
aplicación que hasta el momento tenemos, así cualquier cambio será en un solo archivo y
no en varios, finalmente lo exportamos como un módulo:
// server/config/index.js
const config = {
 server: {
 port: 3000,
 },
};
module.exports = config;
Para esta aplicación el hostname no es obligatorio, por lo cual lo omitimos en la
configuración.
De vuelta en nuestro archivo raíz, lo reescribimos quitándole las múltiples responsabilidades
que tenía y dejándolo solo con la responsabilidad de iniciar cada uno de los componentes
de la aplicación, utilizando los módulos creados posteriormente:
Utilizando Express JS
22
// index.js
const http = require('http');
const app = require('./server');
const config = require('./server/config');
const { port } = config.server;
const server = http.createServer(app);
server.listen(port, () => {
 console.log(`Server running at port: ${port}`);
});
Como podemos observar la librería http es compatible con la versión de servidor nuestra
aplicación ( app ) creada por Express JS, que se crea en la función createServer e
inclusive si en el futuro fuera a incorporar un servidor https , solo es incluir la librería y
duplicar la línea de creación del servidor.
Reiniciamos nuestro servidor e iniciamos en modo desarrollo:
npm run dev
Si visitamos nuevamente el navegador en la dirección http://localhost:3000 todo debe
estar funcionando sinningún problema.
Antes de finalizar hacemos commit de nuestro trabajo
git add --all
git commit -m "Add Express JS and configuration file"
Más información:
Express JS
Utilizando Express JS
23
http://expressjs.com/
Configuración y variables de entorno
Cross-env
Nuestra aplicación no siempre se ejecutará en ambiente desarrollo, por lo cual debemos
establecer cuál es el ambiente en que se está ejecutando y dependiendo de ello podemos
utilizar diferentes tipos de configuración e incluso conectarnos a diferentes fuentes de datos,
para ello vamos a utilizar la variable de entorno process.NODE_ENV, que puede ser accedida
desde cualquier parte de nuestra aplicación por el objeto global process .
Es muy importante garantizar la compatibilidad entre los diferentes sistemas operativos ya
que cada sistema establece las variables de manera diferente, para asegurar que funcione
en diferentes sistemas operativos utilizaremos una librería de npm llamada cross-env , la
instalamos como una dependencia de nuestra proyecto y así aseguramos que siempre esté
disponible en nuestra aplicación:
npm i -S cross-env
Entonces finalmente para utilizarla modificaremos nuestros npm scripts en el archivo
 package.json de la siguiente manera:
"scripts": {
 "dev": "cross-env NODE_ENV=development nodemon --inspect",
 "start": "cross-env NODE_ENV=production node index",
 "test": "cross-env NODE_ENV=test echo \"Error: no test specified\"&& exit 1"
},
Antes de finalizar hacemos commit de nuestro trabajo:
git add --all
git commit -m "Add cross-env and setting the environments"
Más información acerca de:
process
cross-env
Dotenv
Configuración y variables de entorno
24
https://nodejs.org/api/process.html
https://www.npmjs.com/package/cross-env
En este momento nuestras variables de configuración están almacenadas en un archivo
JavaScript y sus valores están marcados en el código fuente de la aplicación, lo cual NO es
una buena práctica pues se estaría exponiendo datos sensibles, la idea es extraer esta
información y tenerla en archivos separados por entorno pues no es la misma base de
datos de desarrollo que la de producción.
Ahora debemos distinguir que es configuración de sistema y del usuario, la primera son
valores que necesita la aplicación para funcionar como: numero del puerto, la dirección IP y
demás, las segundas son más relacionadas con la personalización de la aplicación como:
número de objetos por página, personalización de la respuesta al usuario, etc.
Utilizaremos una librería que nos permite hacer esto muy fácil llamada dotenv, que nos
carga todas las variables especificadas en un archivo a el entorno de Node JS, procedemos
a crear el archivo de configuración en la raíz del proyecto llamado .env y le colocamos el
siguiente contenido:
SERVER_PORT=3000
Instalamos la librería:
npm i -S dotenv
Procedemos a modificar nuestro archivo de configuración:
// server/config/index.js
require('dotenv').config('');
const config = {
 server: {
 port: process.env.SERVER_PORT || 3000,
 },
};
module.exports = config;
Como podemos ver por defecto la librería dotenv buscará el archivo llamado .env ,
cargará las variables y su valores en el entorno del proceso donde se está ejecutando Node
JS. Así nuestra configuración del sistema no estará más en el código fuente de la aplicación
y adicional podemos reemplazar el archivo de configuración en cada entorno donde se
publique la aplicación.
Configuración y variables de entorno
25
Antes de terminar un aspecto muy importante es NO incluir el archivo de configuración
 .env en el repositorio de git, pues esto es justo lo que queremos evitar: que nuestra
configuración y valores no quede en el código fuente de la aplicación, para ello modificamos
el archivo .gitignore de la siguiente forma:
node_modules/
.DS_Store
.Thumbs.db
.env
Como no estamos guardando este archivo en nuestro repositorio, crearemos una copia de
ejemplo para que el usuario pueda renombrarlo y utilizarlo:
cp .env .env.example
Antes de finalizar hacemos commit de nuestro trabajo:
git add --all
git commit -m "Add dotenv, .env file and set in the configuration file"
Más información acerca de:
dotenv
Configuración y variables de entorno
26
https://github.com/motdotla/dotenv
Middleware para manejo de errores
Hasta el momento la única ruta que tenemos configurada en nuestro proyecto con express
es / (La raíz del proyecto) y con el método GET, pero ¿Qué sucede si tratamos de
acceder otra ruta?. Pues en este momento no tenemos configurado como manejar esta
excepción, en términos de express no existe ningún "Middleware" que la procese, por lo
tanto terminará en un error no manejado por el usuario.
Si vamos al navegador y tratamos de acceder a una ruta como localhost:3000/posts 
obtendremos un resultado generico cómo: Cannot GET /posts y un código HTTP 404 que
indica que el recurso no fue encontrado, pero nosotros podemos controlar esto con express,
primero analizemos el fragmento de código que define la ruta:
app.get('/', (req, res) => {
 res.send('Hello World');
});
1. La aplicación ( app ) adjunta una función callback para cuando se realice una petición a
la ruta / con el método GET.
2. Esta función callback recibe dos argumentos: el objeto de la petición (request)
abreviado req y el objeto de la respuesta (response) abreviado res .
3. Una vez se cumplen los criterios establecidos en la definición de la ruta la función de
callback es ejecutada.
4. Se envía la respuesta por parte del servidor ( res.send('Hello world') ) el flujo de esta
petición se detiene y el servidor queda escuchando por más peticiones.
Express middleware
"Las funciones de middleware son funciones que tienen acceso al objeto de solicitud (req),
al objeto de respuesta (res) y a la siguiente función de middleware en el ciclo de
solicitud/respuestas de la aplicación. La siguiente función de middleware se denota
normalmente con una variable denominada next."
(Tomado de Utilización de middleware)
Esto quiere decir que nuestro callback en realidad es una función middleware de express y
tiene la siguiente firma de parámetros:
Middleware para manejo de errores
27
http://expressjs.com/es/guide/using-middleware.html
app.get('/', (req, res, next) => {
 res.send('Hello World');
});
Dónde next es otra función que al invocarse permite continuar el flujo que habíamos
mencionado antes para que otras funciones middleware pueden continuar con el
procesamiento de la petición.
Esto quiere decir que podemos agregar otro middleware al final del flujo para capturar si la
ruta no fue procesada por ningún middleware definido anteriormente:
// server/index.js
const express = require('express');
const app = express();
app.get('/', (req, res, next) => {
 res.send('Hello World');
});
// No route found handler
app.use((req, res, next) => {
 res.status(404);
 res.json({
 message: 'Error. Route not found',
 });
});
module.exports = app;
Nótese varias cosas importantes en el fragmento de código anterior:
1. Se ha agregado la función next al middleware de la ruta raíz, pero realmente no lo
estamos utilizando dentro de la función, pero esto es con el fin hacer concordancia con
la firma que tienen las funciones middleware (Más adelante la editaremos)
2. Le hemos indicado a la aplicación ( app ) utilizar una función middleware, nuevamente
podemos saber que es una función middleware de express por su firma, revisando los
parámetros.
3. Estamos estableciendo el código HTTP de la respuesta con res.status(404) el cual
equivalente a una página no encontrada, en este caso una ruta o recurso.
4. Cuando un servidor Web responde debe indicar el tipo de contenido que está
devolviendo al cliente (por ejemplo: Navegador Web) para que esté a su vez puede
interpretarlo y mostrarlo al usuario, esto se indica en la respuesta mediante el
encabezado Content-type , por defecto el valor es text/plain que se utiliza para texto
Middleware para manejo de errores
28
plano o text/html para códigoHTML, cada uno de estos valores se conoce como el
MIME Type de los archivos. En este caso estamos respondiendo explícitamente un
objeto tipo JSON como lo indicamos en la función res.json() , no tenemos necesidad
de establecer el Content-Type , pues express lo hace automáticamente por nosotros a
 application/json y adicionalmente convierte el objeto literal de JavaScript a un objeto
JSON como tal.
Guardamos el archivo (nodemon reiniciará nuestra aplicación automáticamente ) y ahora si
vamos nuevamente al navegador e invocamos nuevamente la ruta localhost:3000/posts 
obtendremos el mensaje que establecimos en el middleware que se convierte en la última
función de middleware de todos las declaradas anteriormente en esta aplicación de
express:
{ 
 message: 'Error. Route not found'
}
Para hacer una analogía a la definición de los middleware es como una cola de funciones
donde la petición se revisa desde comenzando desde la primera función, si el middleware
coincide con la petición este detiene el flujo, procesa la petición y puede hacer dos cosas:
dar respuesta a la petición y en este caso se detiene la revisión por completo o dejar pasar
la petición al siguiente middleware esto se hace con la función next, o de lo contrario si
ninguna coincide llegará al final que es el middleware genérico para capturar todas las
peticiones que no se pudieron procesar antes.
Manejo de errores
Ahora surge la siguiente pregunta, ¿Cómo podemos controlar los errores que sucedan en
las diferentes rutas?, express permite definir una función middleware con una firma de
parámetros un poco diferente a las anteriores, introducimos el siguiente código al final:
Middleware para manejo de errores
29
// server/index.js
...
// Error handler
app.use((err, req, res, next) => {
 const {
 statusCode = 500,
 message,
 } = err;
 res.status(statusCode);
 res.json({
 message,
 });
});
module.exports = app;
Los ... en el código indica que hay código anterior en el archivo
Podemos ver varias cosas en el código anterior:
1. El middleware que captura errores en express comienza por el parámetro de error
abreviado como err 
2. Obtenemos el statusCode del objeto err , si no trae ninguno lo establecemos en 500
por defecto que significa Internal Server Error, así como el message que este trae.
3. Finalmente establecemos el StatusCode en la respuesta ( res ) y enviamos de vuelta
un JSON con el mensaje del error.
Ahora cada vez que invoquemos un error en cualquier middleware tendremos uno que lo
capture y procese, podríamos aprovechar esto, por ejemplo, para guardar en los logs
información relevante del error.
Para terminar cambiamos nuevamente la definición de la ruta raíz de la siguiente manera:
...
app.get('/', (req, res, next) => {
 res.json({
 message: 'Welcome to the API',
 });
});
...
Middleware para manejo de errores
30
Nótese que ahora estamos devolviendo un archivo JSON, pues estamos creando un REST
API Web.
Antes de continuar guardemos nuestro progreso:
git add --all
git commit -m "Add middleware for not found routes and errors"
Middleware para manejo de errores
31
Sistema de Logs
Utilizar Winston para los logs
Es importante para cualquier aplicación que va a salir a producción tener un sistema de
logs, pues cuando estamos en desarrollo tenemos el control total y es muy fácil investigar
para averiguar si existe algún error, lo que no sucede cuando la aplicación está
ejecutándose en producción, por lo tanto el sistema de logs es una manera eficaz de dejar
rastro de las operaciones, advertencias o errores que puedan suceder en la aplicación.
Existen muchas librerías que son de mucha utilidad así como express, entre ellos está
Winston, el cual nos permite crear un log personalizado con múltiples métodos para
manejar los logs desde imprimirlos en la consola hasta guardarlos en archivos.
Procedemos a instalar las dependencias
npm install -S winston
Debemos crear un nuevo archivo para establecer la configuración de los logs, estoy es muy
importante si en el día de mañana se quiere cambiar de configuración o inclusive de librería
el resto de la aplicación no debería verse afectada.
Creamos el archivo con su respectivo contenido:
// server/config/logger.js
const { createLogger, format, transports } = require('winston');
// Setup logger
const logger = createLogger({
 format: format.simple(),
 transports: [new transports.Console()],
});
module.exports = logger;
Como pueden observar hemos decidido para este proyecto imprimir los logs en la consola,
pero como mencionamos antes esta librería nos permite almacenarlos en archivos, si así lo
deseamos.
Sistema de Logs
32
// server/index.js
const express = require('express');
const logger = require('./config/logger');
// Init app
const app = express();
// Routes
app.get('/', (req, res, next) => {
 res.json({
 message: 'Welcome to the API',
 });
});
// No route found handler
app.use((req, res, next) => {
 const message = 'Route not found';
 const statusCode = 404;
 logger.warn(message);
 res.status(statusCode);
 res.json({
 message,
 });
});
// Error handler
app.use((err, req, res, next) => {
 const { statusCode = 500, message } = err;
 logger.error(message);
 res.status(statusCode);
 res.json({
 message,
 });
});
module.exports = app;
Como podemos observar en el fragmento de código anterior incluimos nuestro logger y lo
utilizamos en los middleware.
Antes de continuar guardemos nuestro progreso:
git add --all
git commit -m "Add Winston to create log system"
Sistema de Logs
33
Más información:
Winston
Utilizar morgan para hacer logs de las
peticiones
La librería anterior nos permitió crear un log personalizado, pero también debemos a
registrar el acceso a todas las peticiones dándonos un detalle de lo que se está recibiendo
en el servidor, para esto vamos a utilizar la librería de morgan, procedemos a instalarla de la
siguiente manera:
npm install -S morgan
Morgan finalmente es un middleware que tomará las peticiones y las imprimirá según
nosotros le indiquemos:
// server/index.js
const express = require('express');
const morgan = require('morgan');
const logger = require('./config/logger');
// Init app
const app = express();
// Setup middleware
app.use(morgan('combined'));
// Routes
app.get('/', (req, res, next) => {
 res.json({
 message: 'Welcome to the API',
 });
});
...
Incluimos morgan como un middleware, probamos accediendo a diferentes URL y vemos el
resultado en la consola, pero tenemos dos salidas de logs: una producida por winston y otra
por morgan, que casualmente ambas están saliendo a la consola por lo tanto añadimos la
siguiente configuración adicional:
Sistema de Logs
34
https://www.npmjs.com/package/winston
// server/index.js
...
// Setup middleware
app.use(morgan('combined', { stream: { write: message => logger.info(message) } }));
...
Resulta y pasa que por defecto morgan coloca sus logs en la consola, pero nosotros le
estamos indicando que utilice el logger que creamos con winston, finalmente irán a la
consola, pero esta vez es un solo manejador de logs, que si decidimos guardarlos en
archivos, nuevamente el cambio sería solamente en una sola parte.
Si abrimos el navegador e invocamos diferentes rutas en la dirección del navegador Web
podremos ver en la consola las diferentes peticiones que llegan al servidor y también los
mensajes personalizados que imprimimos en los middleware.
Antes de continuar guardemos nuestro progreso:
git add --all
git commit -m "Add morgan to log the app requests"
Más información:
Morgan
Hacer seguimiento a las peticiones
Nuevamente cuando nuestra aplicación está ejecutándose en producción llegan
muchísimas peticiones al mismo tiempo y si deseamos identificar una en particular va a
hacer muy difícil, para ello debemos identificar cada uno de ellas, utilizaremos una librería
para ello llamada express-request-id que añade a la petición entrante (req) un campo
llamado id donde su valor es un número único yen la petición de respuesta (res) establece
el encabezado X-Request-Id con el mismo valor para que el cliente pueda identificar la
petición.
procedemos a instalarla de la siguiente manera:
npm install -S express-request-id
La incluimos en nuestro archivo del servidor:
Sistema de Logs
35
https://www.npmjs.com/package/morgan
// server/index.js
const express = require('express');
const morgan = require('morgan');
const requestId = require('express-request-id')();
const logger = require('./config/logger');
// Init app
const app = express();
// Setup middleware
app.use(requestId);
app.use(morgan('combined', { stream: { write: message => logger.info(message) } }));
...
Al redirigir los mensajes de morgan a winston las peticiones siempre agregan un final de
línea, por lo tanto vamos a removerlo con la siguiente librería:
npm i -S strip-final-newline
Ahora debemos incluirla en nuestro log de morgan, por lo tanto vamos a mover morgan a
nuestro archivo de logger y lo editamos de la siguiente manera:
Sistema de Logs
36
// server/config/logger.js
const { createLogger, format, transports } = require('winston');
const morgan = require('morgan');
const stripFinalNewline = require('strip-final-newline');
// Setup logger
const logger = createLogger({
 format: format.simple(),
 transports: [new transports.Console()],
});
// Setup requests logger
morgan.token('id', req => req.id);
const requestFormat = ':remote-addr [:date[iso]] :id ":method :url" :status';
const requests = morgan(requestFormat, {
 stream: {
 write: (message) => {
 // Remove all line breaks
 const log = stripFinalNewline(message);
 return logger.info(log);
 },
 },
});
// Attach to logger object
logger.requests = requests;
module.exports = logger;
Explicamos a continuación los cambios:
Le indicamos a morgan como va a calcular el token :id , que como vimos
anteriormente nuestra nueva librería lo agrega al objeto req .
Establecemos un formato personalizado basado en el combined que trae por defecto y
añadimos el token :id .
Eliminamos todos los saltos de línea y guardamos toda la configuración de morgan en
la variable requests .
Finalmente guardamos dentro de logger la configuración de morgan, pues recordemos
que debemos añadirla como middleware de express en el archivo del servidor.
Actualizamos nuevamente nuestro archivo del servidor:
Sistema de Logs
37
// server/index.js
const express = require('express');
const requestId = require('express-request-id')();
const logger = require('./config/logger');
// Init app
const app = express();
// Setup middleware
app.use(requestId);
app.use(logger.requests);
...
Antes de continuar guardemos nuestro progreso:
git add --all
git commit -m "Add Request id to identify the request with morgan"
Más información:
express-request-id
Añadir formato de la petición a los logs
En este momento los logs generados por morgan al recibir las peticiones tienen una
información adicional de la petición como: Dirección IP del cliente, fecha y hora, el método
HTTP y la URL, añadimos un función que reciba la petición como parámetro y nos devuelva
la misma información:
// server/config/logger.js
...
// Attach to logger object
logger.requests = requests;
// Format as request logger and attach to logger object
logger.header = (req) => {
 const date = new Date().toISOString();
 return `${req.ip} [${date}] ${req.id} "${req.method} ${req.originalUrl}"`;
};
module.exports = logger;
Sistema de Logs
38
https://github.com/floatdrop/express-request-id
A continuación vamos a realizar las siguientes modificaciones en nuestro archivo del
servidor en los dos middleware asociados a errores:
// server/index.js
...
// No route found handler
app.use((req, res, next) => {
 next({
 message: 'Route not found',
 statusCode: 404,
 level: 'warn',
 });
});
// Error handler
app.use((err, req, res, next) => {
 const { message, statusCode = 500, level = 'error' } = err;
 const log = `${logger.header(req)} ${statusCode} ${message}`;
 logger[level](log);
 res.status(statusCode);
 res.json({
 message,
 });
});
module.exports = app;
Lo primero que podemos observar es que ahora el middleware que nos captura cuando una
ruta no es encontrada está invocando el siguiente middleware ( next ) con el información
del error y finalmente en procesado por nuestro middleware asociado con los errores, donde
creamos la cadena de log basada en la función que acabamos de crear, una nueva
modificación es el type que por defecto es error si no es enviada en el objeto de error .
Si vamos al navegador e invocamos una dirección que no existe sobre nuestra API
podemos ver el resultado en los logs.
Antes de continuar guardemos nuestro progreso
git add --all
git commit -m "Add log header from requests"
Sistema de Logs
39
	Introducción
	¿Que es un API?
	Inicializando el proyecto
	Herramientas de desarrollo
	Crear un simple Web Server
	Utilizando Express JS
	Configuración y variables de entorno
	Middleware para manejo de errores
	Sistema de Logs
	Postman
	Utilizando el Router y Routes de Express
	Creando el layout del API
	Capturando y procesando parámetros de las peticiones
	Patrones asincrónicos de JavaScript
	Callbacks
	Promises
	async / await
	Bases de datos SQL y NoSQL
	Instalando y configurando MongoDB
	Conectando con MongoDB
	Mongoose Models
	Procesando parámetros comunes con middleware
	Estandarización de la respuesta
	Mongoose Schemas
	Paginación
	Ordenamiento
	Creando recursos
	Relaciones entre recursos
	Consultar recursos anidados
	Añadir recursos anidados
	Añadir y remover campos de un documento
	Administración de usuarios
	Autenticación de usuarios
	Autorización de usuarios
	Validaciones personalizadas
	Limpieza de campos
	Restricción de las peticiones
	Pruebas
	Configurando pruebas
	Pruebas unitarias
	Pruebas de integración
	Configurando Swagger y JSDoc
	Documentando las rutas y creando modelos
	Conclusión

Continuar navegando