Cómo construir una API Rest con NodeJS, Express y MySQL

Con conocimientos de JavaScript y MySQL, podemos construir nuestra API NodeJS usando Express.

Investigué un poco, y estuve intentando desarrollar una API desde cero.
Me gusta simplificar las cosas y tratar de evitar la duplicación de código.

Esta Guía le mostrará cómo construir una API desde cero:
Aprenderá cómo crear rutas,
cómo usar mysql2, cómo configurar y conectarse a la base de datos, y cómo ejecutar consultas con sentencias preparadas.
Cómo crear un middleware que pueda obtener un argumento adicional además de req, res y next callback.
Aprenderás a comprobar los datos del objeto request utilizando el módulo Express Validator.
Aprenderá a utilizar el módulo JWT para crear un token para el usuario, verificar el token y obtener el objeto almacenado en el token.
Además, aprenderá a proporcionar a los usuarios permiso para acceder a una determinada ruta en función de sus roles de usuario.

Tecnologías y paquetes:

  • NodeJS
  • Express
  • mysql2
  • bcryptjs
  • jsonwebtoken
  • express-validator
  • dotenv
  • cors

Instalación de MySQL:

Yo uso WSL, y puedes usar este tutorial para ver cómo instalar MySQL en WSL.
Tienes que asegurarte de que MySQL se está ejecutando con este comando:

sudo service mysql status
Entrar en el modo de pantalla completa Salir del modo de pantalla completa

Si no se está ejecutando, simplemente usa:

sudo service mysql start
Entrar en el modo de pantalla completa Salir del modo de pantalla completa

Descripción de la aplicación:

Construiremos una API de descanso para las operaciones CRUD: crear, leer, actualizar y eliminar usuarios.

Crear la carpeta del proyecto e instalar todas las dependencias:

mkdir mysql-node-express && cd mysql-node-expressnpm init -ynpm i express express-validator mysql2 cors dotenv jsonwebtoken -Snpm i nodemon -D
Entrar en el modo de pantalla completa Salir del modo de pantalla completa

Ir al archivo package.json y cambiar el valor “main” por “src/server.js” y añade estos scripts al objeto scripts:

"start": "node src/server.js","dev": "nodemon"
Entrar en modo de pantalla completa Salir del modo de pantalla completa

package.json debería tener el siguiente aspecto:

Crear archivo .env:

Usaremos el archivo .env para gestionar todas nuestras variables de entorno.
El archivo .env es un archivo oculto que nos permite personalizar nuestras variables de entorno utilizando la sintaxis ENV VARIABLE = VALOR.
Estas variables se cargan utilizando el módulo dotenv que ya tenemos instalado.
El fichero .env puede definirse en diferentes etapas del entorno (entornos de desarrollo/etapa/producción).

Creamos el fichero .env, copiamos las siguientes líneas, y actualizamos el fichero con nuestro nombre_db de MySQL, nombre_de_usuario y contraseña:

# DB ConfigurationsHOST=localhostDB_USER=db_usernameDB_PASS=db_passwordDB_DATABASE=db_name# local runtime configsPORT=3000SECRET_JWT=supersecret
Entramos en modo pantalla completa Salimos del modo pantalla completa

Creamos el fichero nodemon.json:

Nodemon es una herramienta que ayuda a desarrollar aplicaciones basadas en node.js reiniciando automáticamente la aplicación node cuando se detectan cambios de archivo en el directorio de destino.
El nodemon es una envoltura de reemplazo para node. En lugar de utilizar el comando node, debemos utilizar el comando nodemon en la línea de comandos para ejecutar nuestro script.
Podemos añadir fácilmente interruptores de configuración mientras se ejecuta nodemon en la línea de comandos, como:

nodemon --watch src
Entrar en modo de pantalla completa Salir del modo de pantalla completa

También podemos utilizar un archivo (nodemon.json) para especificar todos los interruptores.
Si queremos vigilar varios archivos en un directorio, podemos añadir el directorio en el array “watch”.
Si queremos buscar una extensión concreta (como un archivo ts) podemos usar la propiedad “ext”.
Si queremos ignorar algunos archivos, podemos definirlos en el array “ignore”‘, y así sucesivamente…
Yo uso este archivo sobre todo cuando estoy creando un servidor con NodeJS basado en typescript, pero creo que es más fácil tener más sitios donde incluir las configuraciones de nuestra app.
Este archivo es opcional.

Crear el archivo nodemon.json y añadir esto al archivo:

{ "watch": , "ext": ".js", "ignore": }
Entrar en modo de pantalla completa Salir del modo de pantalla completa

Crear la carpeta src:

mkdir src && cd src
Entrar en modo de pantalla completa Salir del modo de pantalla completa

En la carpeta src crear subcarpetas: controladores, modelos, rutas, middleware, db, y utilidades:

mkdir controllers models routes middleware db utils
Entrar en el modo de pantalla completa Salir del modo de pantalla completa

Configurar el servidor Express:

En el directorio src crea el archivo server.js y copia estas líneas:

En este archivo, importamos express para construir las APIs de descanso y usamos express.json() para analizar las peticiones entrantes con cargas útiles JSON.

También importamos el módulo dotenv para leer el archivo .env config para obtener el número de puerto para ejecutar el servidor.

También importamos userRouter.

Después de eso, tenemos un middleware que maneja los errores 404 → si alguien busca un endpoint que no existe, obtendrá este error: ‘Endpoint Not Found’ con el código de estado 404. Después de eso, estamos utilizando el middleware de error que obtendrá los datos de error de las rutas anteriores.Si se llama a next(err), se puede ver el middleware 404 como ejemplo.
Escuchamos el puerto desde el archivo.env y lo imprimimos en la consola que el servidor se está ejecutando.

Crear base de datos MySQL y tabla de usuarios:

En el directorio db, crearemos el archivo create-user-db.sql y copiaremos-pegaremos estas líneas:

En este script, primero damos de baja la base de datos si existe para poder restablecerla rápidamente en caso de error (podéis comentar esa línea si queréis), luego, creamos la base de datos si no existe. La establecemos como nuestra base de datos activa y creamos una tabla “usuario” con todas las columnas (id, nombre de usuario, etc.), permitiendo de nuevo un restablecimiento conveniente si es necesario. Puedes ejecutar esta consulta en tu cliente de base de datos si estás usando uno.

Si estás usando wsl, en el directorio db puedes ejecutar:

mysql -u -p < create-user-db.sql
Entrar en modo de pantalla completa Salir del modo de pantalla completa

Configurar y conectar con la base de datos MySQL:

Crea un archivo adicional en el directorio db llamado db-connection.js, y copia y pega esto:

En este archivo, primero importamos el módulo dotenv y lo usamos para leer la información de configuración de la base de datos como el host db, el usuario db desde el archivo.env.

Comprobamos la conexión en caso de que haya un problema con la base de datos y luego liberamos la conexión.

Tenemos un método query que devuelve una promesa del resultado de la consulta.

Utilizamos un bloque try-catch para capturar los errores comunes de MySQL y devolver los códigos de estado y mensajes HTTP apropiados.

Al final del fichero, creamos una instancia de la clase DBConnection y utilizamos el método query, y en el model.js (que veremos en el siguiente paso), volveremos a utilizar el método query.

Creamos el Error Handler:

A continuación, vamos a crear nuestro manejador de errores.

Para ello, primero, crearemos el archivo HttpException.utils.js bajo el directorio utils, y copiaremos-pegaremos lo siguiente:

La clase HttpException hereda la clase Error.
El constructor obtendrá el estado, el mensaje y los datos. Pasaremos la variable mensaje al constructor padre usando super(mensaje), y luego inicializaremos las variables de instancia estado, mensaje y datos.

Después, crearemos un manejador de errores middleware en el directorio middleware.
Crearemos un archivo error.middleware.js y copiaremos y pegaremos lo siguiente:

Podemos ver en la parte inferior del archivo cómo va a ser el objeto.

El middleware obtendrá req, res, y next callback, pero también obtendrá un argumento adicional, error (usando next(error) antes de que lleguemos a este middleware).

Utilizamos la desestructuración para obtener las variables del objeto error y establecer el estado a 500 si no se ha configurado antes.

Después de esto, si el estado es 500, nos aseguraremos de cambiar el mensaje para que el usuario reciba un mensaje de error interno del servidor genérico sin revelar la naturaleza exacta del fallo.

Después de esto, creamos un objeto de error con las propiedades de tipo, estado y mensaje (los datos son opcionales).

Creamos archivos utils (helpers):

En el directorio utils, creamos dos ficheros más, common.utils.js, y userRoles.utils.js.

common.utils.js:

Esta función ayuda a establecer múltiples campos para consultas preparadas con pares clave-valor.
ColumnSet la matriz de pares clave =?,
Los valores deben por lo tanto estar en el mismo orden que la matriz columnSet.

userRoles.utils.js:

module.exports = { Admin: 'Admin', SuperUser: 'SuperUser'}
Entrar en el modo de pantalla completa Salir del modo de pantalla completa

Crear función Async:

Crea otro archivo llamado awaitHandlerFactory.middleware.js en el directorio del middleware y copia-pega esto:

En general, sabemos que el middleware es sólo un método asíncrono que obtiene los argumentos req, res y next, así que, si queremos que este middleware obtenga un argumento adicional, lo haremos de esta manera (lo usaremos también en el middleware auth en el siguiente paso).

Esta función obtendrá un callback, ejecutará el script del middleware, e intentará disparar este callback en el bloque try.
Si algo va mal aquí, atrapará el error y usaremos el next(err) (que lo transferirá al siguiente middleware => error.middleware.js).

Crear el Middleware de Autenticación:

Otro middleware que necesitamos es el auth middleware que utilizaremos para comprobar los permisos de los usuarios a través del módulo JWT.

De forma similar al middleware awaitHandlerFactory.middleware.js, aquí tenemos un middleware que requiere un argumento adicional (que es opcional) => roles.

He utilizado try-catch para ajustar el estado de error en la zona de captura a 401 (si el token ha caducado, por ejemplo).

Al principio, estamos buscando req.headers.authorization – si no está definido en la cabecera o si la cabecera no comienza con “Bearer “, el usuario recibirá una respuesta 401. Si empieza por “Bearer “, obtendremos el token y usaremos la clave secreta del archivo.env para descifrarlo.

Verificaremos el token usando la función sincrónica jwt.verify, que obtiene el token, y la secretKey, como argumentos y devuelve la carga útil decodificada, si la firma es válida y los campos opcionales de expiración, audiencia o emisor son válidos. En caso contrario lanzará un error.

Ahora, podemos encontrar al usuario con este token buscando el id de usuario.
Si el usuario ya no existe, obtendrá una excepción de 401 sin ninguna información.
Si el usuario existe, comprobaremos si el usuario actual es el propietario que busca sus rutas o si el usuario tiene el rol para acceder a esta ruta.
Guardamos el usuario actual por si quiere obtener sus datos en el siguiente middleware (como la ruta “whoami”).

Validación de datos usando el módulo Express Validator:

En el directorio del middleware, crearemos un archivo adicional que utilizaremos para verificar las propiedades del req.body.

Crea una subcarpeta en el directorio del middleware llamada validators y crea un archivo en este directorio, userValidator.middleware.js. Copia-pega esto:

En este archivo, he utilizado el módulo express-validator, que es muy fácil de usar siempre que necesitemos comprobar algunas propiedades, comprobar si la propiedad existe, o crear comprobaciones personalizadas con un mensaje personalizado para el usuario si algún valor de la propiedad no es válido.

Ahora podemos empezar a crear nuestros archivos de rutas, controladores y modelos.

Define las rutas:

Crea el archivo user.route.js en el directorio de rutas y copia-pega esto:

El ejemplo anterior muestra cómo definir rutas. Vamos a tratar de romperlo en pedazos:

  • Puedes crear un router usando express.Router().Cada ruta puede cargar una función de middleware que maneja la lógica de negocio.UserController, por ejemplo lleva todos los middlewares principales.Para utilizar el enrutador, el enrutador debe ser exportado como un módulo y utilizado en la aplicación principal utilizando app.use(router_module).
  • Utilizamos auth middleware para la autenticación y autorización de usuarios, para comprobar el token de usuario o el rol de usuario para la ruta.En nuestro ejemplo, algunas de las rutas utilizan el auth middleware para comprobar la autenticación y autorización del usuario.Este middleware se activará antes del middleware principal (el que contiene la lógica de negocio).Se debe llamar al siguiente callback para pasar el control al siguiente método del middleware.De lo contrario, la petición se quedará colgada.
  • awaitHandlerFactory (middleware try-catch) se utiliza para envolver todo el middleware asíncrono. De esta manera, si uno de los middleware lanza un error, awaitHandlerFactory atrapará ese error.Puedes ver que todas nuestras funciones middleware están envueltas con el middleware awaitHandlerFactory, lo que nos ayuda a manejar nuestros errores usando try-catch en un solo lugar.
  • Además, tenemos el esquema createUserSchema, updateUserSchema y validateLogin para validar el cuerpo antes de que iniciemos el siguiente middleware.

La sintaxis del método HTTP es:

Crear el controlador:

Crea el archivo user.controller.js en el directorio de controladores y copia y pega esto:

Como se mencionó anteriormente, el archivo del controlador contiene nuestra lógica de negocio para manejar nuestras rutas.
En nuestro ejemplo, algunos métodos utilizan la clase UserModel para consultar la base de datos para obtener los datos.
Para devolver los datos en cada middleware, utilizamos res.send(result) para enviar una respuesta al cliente.

Crea el Modelo:

Y crea el archivo user.model.js en el directorio models y copia-pega esto:

Esta clase hace la conexión entre el controlador y la base de datos.
Aquí tenemos todos los métodos que obtienen los argumentos del controlador, hacen una consulta, preparan sentencias, se conectan a la base de datos usando el método query de la clase db-connection, envían la petición con el array de sentencias preparadas y obtienen el resultado de vuelta.
Cada función devuelve el resultado al controlador.

.gitIgnore:

En caso de que decidas añadir este proyecto a tu GitHub, no olvides crear un archivo .gitignore y copiar-pegar esto:

node_modules.env
Entrar en modo de pantalla completa Salir del modo de pantalla completa

Este archivo sólo indica a git qué archivos debe ignorar.
Debe evitar el directorio node_modules porque es pesado y no es necesario para el repositorio.
Cuando alguien clone este repositorio, utilizará el comando “npm I” para instalar todas las dependencias.
Ignorar el archivo .env es para ocultar tus configuraciones privadas de otros desarrolladores que utilicen tu código.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.