Creando una API REST con Express y Docker

mar. 1, 2024

Este artículo forma parte de una serie:

Express.js es un marco de trabajo minimalista, flexible y proporciona un robusto conjunto de características para aplicaciones web. Con Express, puedes construir soluciones backend eficientes que gestionan la lógica de servidor, rutas, interacciones con bases de datos y mucho más, todo bajo una arquitectura fácil de entender y utilizar.

Es ampliamente utilizado en el desarrollo web debido a su velocidad, flexibilidad y facilidad de uso. Al ser un marco de trabajo minimalista, permite a los desarrolladores crear aplicaciones backend de manera rápida y eficiente. Una de las características distintivas de Express.js es su sistema de enrutamiento sencillo y efectivo, que permite definir rutas y manejar solicitudes HTTP de forma clara y concisa.

Ofrece también una amplia gama de middlewares, que son funciones que se ejecutan durante el ciclo de vida de una solicitud HTTP. Éstos pueden utilizarse para tareas como autenticación, compresión de datos, registro de solicitudes, entre otros. La flexibilidad de Express.js para integrar middlewares de terceros facilita la implementación de funcionalidades adicionales en una aplicación sin tener que realizar una programación compleja desde cero. En este caso utilizamos un Middleware que nos permitirá interpretar el cuerpo de las solicitudes HTTP que contienen datos en formato JSON.

El protocolo HTTP, definido en el RFC 2616, especifica una serie de métodos que se utilizan para realizar operaciones en un servidor web. Los métodos más comunes son GET, POST, PUT, DELETE, y OPTIONS, entre otros. Express.js proporciona métodos para manejar cada uno de estos tipos de solicitudes HTTP, lo que permite a los desarrolladores crear aplicaciones web altamente funcionales y eficientes. Cada uno de estos métodos se utiliza para realizar una operación específica en un recurso en el servidor web.

  • GET: Se utiliza para recuperar datos de un recurso en el servidor web. Por ejemplo, cuando se accede a una página web en un navegador, se realiza una solicitud GET para recuperar el contenido de la página.
  • POST: Se utiliza para enviar datos al servidor web para su procesamiento. Por ejemplo, cuando se envía un formulario en una página web, se realiza una solicitud POST para enviar los datos del formulario al servidor.
  • PUT: Se utiliza para actualizar un recurso en el servidor web. Por ejemplo, cuando se edita una entrada en una base de datos, se realiza una solicitud PUT para actualizar los datos de la entrada.
  • DELETE: Se utiliza para eliminar un recurso en el servidor web. Por ejemplo, cuando se elimina una entrada en una base de datos, se realiza una solicitud DELETE para eliminar los datos de la entrada.
  • OPTIONS: Se utiliza para recuperar los métodos permitidos en un recurso en el servidor web. Por ejemplo, cuando se realiza una solicitud OPTIONS a una página web, se recuperan los métodos permitidos en la página.

Se trata de un estándar muy antiguo y que ha sido actualizado con el paso del tiempo y por tanto ha cambiado desde entonces pero, en esencia, sigue siendo el mismo. Los métodos de solicitud HTTP son fundamentales para el desarrollo de aplicaciones web y móviles, ya que permiten a los desarrolladores realizar operaciones en un servidor web de forma eficiente y segura.

Crear una API REST con Express es sorprendentemente sencillo, nos permite centrarnos en la lógica específica de su aplicación en lugar de los detalles de bajo nivel del manejo de solicitudes HTTP. Vamos a proceder con el ejemplo práctico de cómo desplegar una API REST con Node.js y Express utilizando Docker. Este ejemplo proporcionará a los lectores una guía paso a paso para configurar, desarrollar y desplegar nuestra propia API.

Asegúrate de tener Docker instalado en tu sistema. Sigue las instrucciones de instalación de nuestro artículo de introducción si aún no lo has hecho. Para empezar crea una nueva carpeta src para tu proyecto y navega a ella, dentro crea un archivo app.js e inicializamos el proyecto utilizando npm.


    npm init -y
  

Luego utilizamos la misma herramienta para instalar Express:


    npm install express
    

Dentro de la carpeta del proyecto, hemos creado el fichero app.js con el código fuente de nuestra API. Lo abriremos con nuestro editor de texto o IDE preferido y añadiremos el siguiente código para crear una API REST básica con un simple endpoint en la ruta raíz para empezar:


    const express = require('express');
    const app = express();
    const PORT = 3000;
    
    app.use(express.json());
    
    app.get('/', (req, res) => {
        res.send('¡Hola Mundo con Express y Docker!');
    });
    
    app.listen(PORT, () => {
        console.log(`Servidor corriendo en http://localhost:${PORT}`);
    });
    

El código anterior crea un servidor Express que escucha en el puerto 3000 y responde con el mensaje “¡Hola Mundo con Express y Docker!” cuando se accede a la ruta raíz utilizando el método GET del protocolo HTTP. La línea app.use(express.json()) es un middleware necesario para que Express pueda interpretar el cuerpo de las solicitudes HTTP que contienen datos en formato JSON. Ahora que hemos creado nuestra API, vamos a crear un archivo Dockerfile para definir cómo se construirá nuestra imagen de Docker. Dentro de la carpeta del proyecto, crea un archivo llamado Dockerfile y añade el siguiente contenido:


    FROM node:14-alpine
    WORKDIR /app
    COPY package*.json ./
    RUN npm install
    COPY . .
    EXPOSE 3000
    CMD ["node", "app.js"]
    

Este fichero es el que indica a Docker cómo construir la imagen de nuestra aplicación. En este caso, estamos utilizando la imagen oficial de Node.js en su versión 14 basada en Alpine Linux. Este Docker se encarga de crear un directorio de trabajo llamado /app dónde instalará las dependencias de nuestra aplicación con npm install utilizando el package.json que copiará de nuestra carpeta de trabajo. A continuación, copiará el resto de los ficheros de la aplicación a la carpeta de trabajo y expondrá el puerto 3000 para hacerlo accesible desde el host principal. Por último, ejecutará el comando node app.js para iniciar el servidor Express.

Con esto ya estamos listos para Dockerizar nuestra aplicación y hacer el despliegue, en entorno local en este caso, aunque sólo bastarían unos cuántos ajustes para poder hacer el despliegue a producción. Para construir la imagen de Docker, ejecutamos el siguiente comando en la terminal:


    docker build -t library-api .
    

Esto creará una imagen de Docker llamada library-api basándose en el Dockerfile que hemos creado. Una vez que la imagen se haya construido, podemos ejecutar un contenedor de Docker basado en ella con el siguiente comando:


    docker run -p 3000:3000 library-api
    

De esta forma lanzaremos un contenedor Docker que escucha en el puerto 3000 y redirige las solicitudes a nuestra aplicación gestionando la solicitud HTTP recibida en base a lo que el código fuente determine, en este caso, devolver el mensaje “¡Hola Mundo con Express y Docker!”. Si todo ha ido bien, nada más lanzar el contenedor Docker deberíamos ver en la terminal:

Servidor corriendo en http://localhost:3000.

Podemos probar accediendo desde nuestro navegador preferido a la dirección URL indicada y deberíamos ver el mensaje de bienvenida.

Ahora vamos a crear endpoints más complejos para nuestra API, por ejemplo, gestionaremos el catálogo ficticio de una biblioteca de libros ,para ello creamos una variable para almacenar la lista de libros con su id, título, autor, año y disponibilidad. Esta lista, en un principio, se almacenará en la memoria del servidor, en este caso nuestro contenedor Docker, por lo que sólo estarán disponibles mientras el contenedor esté en ejecución. Añadimos el siguiente código al archivo app.js:


    let books = [
        {id: 1, title: "El Quijote", author: "Miguel de Cervantes", year: 1605, available: true},
        {id: 2, title: "Cien años de soledad", author: "Gabriel García Márquez", year: 1967, available: true}
    ];
    

Con estos datos de inicio vamos a modificar el fichero app.js para añadir el endpoint que nos permita acceder a la lista de libros:


    app.get('/books', (req, res) => {
        res.status(200).json(books);
    });
    

Para poder aplicar los cambios y hacer uso de nuestro nuevo endpoint, tendremos que parar el contenedor Docker que está en ejecución y volver a construir la imagen y lanzarlo de nuevo. Para ello, ejecutamos por separado los siguientes comandos en la terminal:


    docker stop $(docker ps -a | grep "library-api" | awk '{print $1}')
    docker build -t library-api .
    docker run -p 3000:3000 library-api
    

Estos comandos se encargan de detener el contenedor que está en ejecución por su nombre gracias a la combinación con grep y awk, reconstruir nuestra imagen de Docker y lanzarla de nuevo. Ahora, tras acceder a la dirección URL http://localhost:3000/books, deberíamos ver la lista de libros en formato JSON:


    [
      {
        "id": 1,
        "title": "El Quijote",
        "author": "Miguel de Cervantes",
        "year": 1605,
        "available": true
      },
      {
        "id": 2,
        "title": "Cien años de soledad",
        "author": "Gabriel García Márquez",
        "year": 1967,
        "available": true
      }
    ]
  

Para no tener que andar parando y volviendo a lanzar el contenedor cada vez que hagamos un cambio en el código fuente, vamos a agregar todos los endpoint que necesitemos y lo reiniciamos:


    // Crear un nuevo libro
    app.post('/book', (req, res) => {
        const {title, author, year, available} = req.body;
        const newBook = {id: books.length + 1, title, author, year, available};
        books.push(newBook);
        res.status(201).send(newBook);
    });
    
    // Crear libros en bloque
    app.post('/books', (req, res) => {
        const newBooks = req.body.books;
        newBooks.forEach(book => {
            const newBook = {
                id: books.length + 1,
                title: book.title,
                author: book.author,
                year: book.year,
                available: book.available
            };
            books.push(newBook);
        });
        res.status(201).json(newBooks);
    });
    
    // Actualizar un libro
    app.put('/books/:id', (req, res) => {
        const {id} = req.params;
        const {title, author, year, available} = req.body;
        const book = books.find(book => book.id === parseInt(id));
        if (book) {
            book.title = title;
            book.author = author;
            book.year = year;
            book.available = available;
            res.status(200).json(book);
        } else {
            res.status(404).send('Libro no encontrado');
        }
    });
    
    // Eliminar un libro
    app.delete('/books/:id', (req, res) => {
        const {id} = req.params;
        const book = books.find(book => book.id === parseInt(id));
        if (book) {
            books = books.filter(book => book.id !== parseInt(id));
            res.status(200).send('Libro eliminado');
        } else {
            res.status(404).send('Libro no encontrado');
        }
    });
    

Tras reiniciar el contenedor Docker, podremos probar los nuevos endpoints con un cliente HTTP como puede ser Insomnium, Postman o cualquier otro. Podremos crear, actualizar y eliminar libros de nuestra biblioteca ficticia, en nuestro caso elegimos Insomnium, un cliente HTTP de código abierto y multiplataforma que nos permite realizar peticiones HTTP a nuestra API y ver las respuestas desde nuestro equipo local sin requisitos de cuentas en la nube o servidores ajenos como intermediarios.

Podemos realizar una solicitud POST a la dirección URL http://localhost:3000/book con el siguiente cuerpo para añadir un nuevo libro a nuestra biblioteca:


    {
      "id": 3,
      "title": "1984",
      "author": "George Orwell",
      "year": 1949,
      "available": false
    }
  

Si todo ha ido bien, deberíamos recibir una respuesta con el nuevo libro añadido similar al cuerpo de la solicitud. En la imagen a continuación podemos ver cómo hemos añadido dos nuevos libros a nuestra biblioteca ficticia utilizando Insomnium junto a su respuesta debajo:

También podemos crear una solicitud para obtener la lista de libros actualizada:

A partir de aquí ya podremos hacer uso de las demás operaciones CRUD (Create, Read, Update, Delete) para gestionar nuestra lista de libros. Con esto hemos creado una API REST con Node.js y Express y la hemos desplegado en un contenedor Docker. Hemos creado endpoints para acceder a la lista de libros, añadir nuevos libros, actualizar libros existentes y eliminar libros. Hemos utilizado Insomnium para probar nuestra API y hemos visto cómo podemos realizar operaciones CRUD en nuestra API utilizando un cliente HTTP. Este es un ejemplo sencillo de cómo podemos crear una API REST con Node.js y Express y desplegarla en un contenedor Docker. Espero que este artículo te haya resultado útil y te haya proporcionado una guía paso a paso para crear tu propia API REST con Node.js y Express.

En el próximo capítulo veremos como desplegar la misma API pero con persistencia de datos utilizando una base de datos virtualizada con Docker y utilizando docker-compose en vez de Docker directamente, lo que nos permitirá tener una arquitectura más compleja y realista incluso combinando diferentes servicios en un mismo contenedor para una gestión más eficiente de nuestros datos y proyectos.

Si tienes alguna pregunta o comentario, no dudes en dejarlo en la sección de comentarios a continuación. ¡Happy Coding!

Artículos relacionados

Quizá te puedan interesar

December 1, 2023

Descubriendo Docker

Docker es una plataforma de virtualización que ofrece una metodología única para empaquetar y …

leer más