BasesDeDatos

Mapeo objeto-relacional en Node.js con Sequelize

Por: Pedro Cruz

Introducción

El uso de las bases de datos se considera importante para el desarrollo de aplicaciones, ya que cumple la función de almacenar la información del sistema y está diseñado para operar con acciones que permiten interactuar con el cliente de la aplicación, esto puede ir más allá de realizar una consulta de la información. Generalmente, estas acciones pueden ser el ingreso de nuevos datos, la modificación de cierta información o la eliminación de la misma. Para el desarrollo con Node.js se cuenta con estas acciones descritas anteriormente, al momento de utilizar una base de datos, ya que existen diferentes opciones de módulos que trabajan con los diferentes sistemas de gestión de bases de datos como MySQL, MariaDB, PostgreSQL, MongoDB, etc.

Para trabajar con la interacción de la base de datos, desde el entorno de Node.js, existen diferentes maneras de realizar esto, uno de ellos es el uso del módulo que corresponde al conector (código que realiza la conexión) con el sistema de base de datos y crear las consultas nativas mediante el uso de un lenguaje de consulta de los datos, el más conocido es el SQL (siglas en inglés de lenguaje de consulta estructurada), que permite administrar y recuperar información de los sistemas de gestión de bases de datos. Otra manera que se ha trabajado es mediante el manejo del modelo de objetos relacionales (abreviado en inglés como ORM) para bases de datos relacionales o el modelo de datos de objetos (abreviado en inglés como ODM) para bases de datos orientados a documentos, con el objetivo de procesar y representar (denominado también como mapeo) los datos como objetos en el lenguaje de programación JavaScript para este entorno.

El objetivo de este mapeo es integrar la programación de datos de tipo objeto en el lenguaje de programación y no depender del lenguaje de consulta para la base de datos, por lo tanto, esta publicación se enfocará con este tipo de integración para una base de datos relacional y como realizar algunas acciones con el desarrollo de una aplicación en Node.js con Express.

Uso de Sequelize ORM

Las soluciones basadas en el mapeo de objetos relacionales para el entorno de Node.js son diversas y van desde soluciones específicas para una base de datos, hasta soluciones que admiten diferentes sistemas de bases de datos. Entre estas soluciones se encuentra la denominada como Sequelize ORM, el cual esta basado en el concepto de promesas (es un objeto que representa la terminación o el fracaso de una operación asíncrona) para Node.js, es compatible con PostgreSQL, MySQL, MariaDB, SQLite y Microsoft SQL Server.

Es interesante manejar este ORM, ya que puede manejar diferentes bases de datos relacionales haciendo una abstracción de los objetos para su programación en las diferentes acciones que se buscan realizar con las bases de datos. Para tener instalado el módulo de Sequelize ORM primero se debe instalar el conector de la base de datos que se utilizará, en la terminal se tendrá que ejecutar alguno de los siguientes comandos:

NOTA: En este artículo se creará un proyecto de Node.js con una conexión a PostgreSQL, si se desea seguir el ejemplo, se debe ejecutar en la raíz del proyecto los comandos indicados posteriormente.

$ npm install --save mysql2 // MySQL
$ npm install --save pg pg-hstore // PostgreSQL
$ npm install --save tedious // MSSQL

Se procede a instalar el módulo del ORM con el siguiente comando:

$ npm install --save sequelize

Si se ha ejecutado los comandos de manera correcta, se guardará la información de los módulos en el archivo package.json del proyecto.

Construcción de la base de datos

Para profundizar en las acciones que se pueden realizar generando un servidor con Express y su interacción con la base de datos, en este caso, se procede a realizar un ejemplo donde se integra el sistema de base de datos PostgreSQL y se plantea la necesidad de trabajar con información al realizar peticiones, por lo tanto, se plantea el ejemplo de construir una base de datos con la información sobre usuarios.

Primero se crea la base de datos, en este caso se genera con el nombre de datos y la tabla se denomina con el nombre de usuarios, para este último, se generan 4 registros de información como se indica en el siguiente tabla:

Tabla usuarios

id nombre edad
1 Diana 15
2 Noemi 20
3 Blanca 25
4 Laura 30
5 Tania 35

NOTA: Para la instalación y un manejo adecuado de la linea de comandos de PostgreSQL puede revisar en el siguiente enlace: Cómo instalar y utilizar PostgreSQL en Ubuntu 18.04.

Si ya se tiene instalado PostgreSQL en el entorno donde va interactuar con el servidor, se podrá acceder a la linea de comandos para la base de datos, escribiendo en la terminal (usando Ubuntu):

$ sudo -u postgres psql

Si se ha ingresado a la sesión interactiva de PostgreSQL, entonces ejecutamos el comando para crear la base de datos, con el nombre ‘datos’:

postgres=# CREATE DATABASE datos;

Después se procede a cambiarse a la base de datos creada en el paso anterior con el siguiente comando:

postgres=# \c datos

Luego se realiza la ejecución del comando para la creación de la tabla ‘usuarios’ con la siguiente estructura propuesta:

datos=# CREATE TABLE usuarios (
            id serial PRIMARY KEY,
            nombre varchar (25) NOT NULL,
            edad smallint NOT NULL
        );

Finalmente se realiza el comando para insertar la información propuesta para el ejemplo, la estructura para el ingreso de datos es el siguiente:

datos=# INSERT INTO usuarios (nombre, edad) VALUES 
            ('Diana', 15),
            ('Noemi', 20),
            ('Blanca', 25),
            ('Laura', 30),
            ('Tania', 35);

Generación del proyecto en Node.js y Express

Ya que se tiene la base de datos, con la información de ejemplo, ahora se procede a la creación del servidor con Node.js y Express, para llevar a cabo esto se debe crear la carpeta denominada como "servidor", a través del comando:

$ mkdir servidor

Luego se debe iniciar la configuración del archivo package.json, a través de los comandos:

$ cd servidor
$ npm init

El último comando le pide los datos para configurar el proyecto de Node.js, sin embargo, falta correr los módulos de Express, el conector de la base de datos PostgreSQL y el módulo del ORM con los siguientes comandos:

$ npm install --save express body-parser morgan
$ npm install --save pg pg-hstore
$ npm install --save pg sequelize

Después de instalar las dependencias del proyecto, se procede a crear el archivo para programar el servidor de Node.js mediante el siguiente comando:

$ touch index.js

Si todo se ha generado de manera correcta, entonces hemos terminado el proceso de instalación y generación de elementos que requiere el proyecto para su funcionamiento.

Construcción de modelo, controladores y rutas para el servidor

Primero vamos a configurar el modelo para la tabla "usuario", donde vamos a realizar la representación de los datos para la creación de objetos, para esto se crea la carpeta "models", se ingresa a la carpeta y se crea el archivo "usuario.js", este proceso está representado en los siguientes comandos:

$ mkdir models
$ cd models
$ touch usuario.js

Dentro del archivo de "usuario.js" vamos a tener la siguiente configuración:

module.exports = (sequelize, DataTypes) => {
    // Se define el objeto usuario con sus propiedades
    const Usuario = sequelize.define('usuario', {
        id: { // Identificador del usuario
            allowNull: false,
            autoIncrement: true,
            primaryKey: true,
            type: DataTypes.INTEGER
        },
        nombre: { // Nombre del usuario
            allowNull: false,
            type: DataTypes.STRING
        },
        edad: { // Edad del usuario
            allowNull: false,
            type: DataTypes.INTEGER
        }
    }, { // Condiciones del objeto con relación a la tabla de los datos
        timestamps: false,
        freezeTableName: true,
        tableName: 'usuarios',
        classMethods: {}
    });
    Usuario.associate = function(models) {
        // Las asociaciones con otros objetos deben ser definidos aquí.
    };
    return Usuario;
};

Ya que contamos con la configuración del objeto que representa a los usuarios, se procede a trabajar en el controlador, que actuará como intermediario entre el modelo y la ruta, contando con un flujo de información entre los elementos mencionados anteriormente y las acciones para manejar los datos.

El proceso consiste en crear la carpeta denominada como "controllers" y dentro de la carpeta, generar el archivo denominado "usuario.js", este proceso se muestra con los siguientes comandos:

$ cd ..
$ mkdir controllers
$ cd controllers
$ touch usuario.js

Después se genera el código para el controlador, como se ve en las siguientes líneas de código:

const Sequelize     = require('sequelize');
const usuario       = require('../models').usuario; // Uso del modelo para los usuarios

module.exports = {
    create(req, res) {
        return usuario
            .create ({ // Crear un nuevo usuario en la tabla
                    nombre: req.params.nombre,
                    edad: req.params.edad
            })
            .then(usuario => res.status(200).send(usuario))
            .catch(error => res.status(400).send(error))
    },
    list(_, res) { // Mostrar lista de usuarios en la tabla
        return usuario.findAll({})
            .then(usuario => res.status(200).send(usuario))
            .catch(error => res.status(400).send(error))
    },
    findName(req, res) { // Mostrar datos de un usuario en la tabla
        return usuario.findAll({
            where: {
                nombre: req.params.nombre,
            }
        })
        .then(usuario => res.status(200).send(usuario))
        .catch(error => res.status(400).send(error))
    },
    updateName(req, res) { // Edición del nombre de un usuario en la tabla
        return usuario.update({ nombre: req.params.nombre }, {
            where: {
                id: req.params.id,
            }
        })
        .then(usuario => res.status(200).send(usuario))
        .catch(error => res.status(400).send(error))
    },
    destroy(req, res) { //  Eliminación de un usuario en la tabla
        return usuario.destroy({
            where: {
                id: req.params.id,
            }
        })
        .then(usuario => 
            res.send({ message: 'Usuario eliminado.' }))
        .catch(error => res.status(400).send(error))
    }
};

Una vez que ya se trabajó con el controlador acerca de las acciones que se permitirán en el servidor, entonces se generan las rutas que se van a crear para el servidor y que puedan estar disponibles por el cliente que realiza las peticiones. Para realizar las rutas, se debe crear la carpeta denominada "routes" y crear el archivo llamado "index.js", esto se puede realizar con los siguientes comandos:

$ cd ..
$ mkdir routes
$ cd routes
$ touch index.js

Luego se genera la programación de las rutas, como se muestra en el siguiente código:

const controladorUsuario = require('../controllers/usuario.js'); // Uso del controlador para realizar acciones con la información de los usuarios
module.exports = (app) => {
   app.get('/api', (req, res) => res.status(200).send ({
        message: 'Utiliza las rutas para obtener los datos de los usuarios.',
   })); // Ruta de inicio
   app.post('/api/crear/usuario/nombre/:nombre/edad/:edad', controladorUsuario.create); // Ruta de creación de usuario
   app.get('/api/listar/usuarios', controladorUsuario.list); // Ruta de lista de usuarios
   app.get('/api/buscar/usuario/nombre/:nombre', controladorUsuario.findName); // Ruta de búsqueda de un usuario por nombre
   app.put('/api/actualizar/usuario/id/:id/nombre/:nombre', controladorUsuario.updateName); // Ruta de edición de nombre del usuario
   app.delete('/api/borrar/usuario/id/:id', controladorUsuario.destroy); // Ruta de eliminación de usuario
};

Integración con el servidor, configuración con la base de datos y pruebas con las rutas

Ya que se ha trabajado con el modelo, el controlador y la ruta, se procede a generar los archivos que integran al servidor, con referencia al archivo "index.js" en la raíz del proyecto, al archivo "index.js" en la carpeta de models y el archivo "config.json" dentro de una carpeta denominada "config", para esto se generan los siguientes comandos:

$ cd ..
$ cd models
$ touch index.js 
$ # Se codifica el archivo
$ cd ..
$ mkdir config
$ cd config
$ touch config.json
$ # Se codifica el archivo
$ cd ..
$ # Se codifica el archivo index.js de la raíz

La referencias del código de cada archivo son las siguientes:

models/index.js

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'test';
const config = require(__dirname + '/../config/config.json')[env];
const db = {}

let sequelize;
if (config.use_env_variable) { // Busca las variables para conectar a la base de datos
    sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
    sequelize = new Sequelize(config.database, config.username, config.password, config);
}

fs // Revisa los archivos que se generaron en la carpeta, para representar la información de los modelos
  .readdirSync(__dirname) 
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
      const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
      db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
    if (db[modelName].associate) {
        db[modelName].associate(db);
    }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

config/config.json

{
    "test": {
        "username": "postgres",
        "password": "123asdZXC_",
        "database": "datos",
        "host": "localhost",
        "dialect": "postgres"
    }
}

servidor/index.js

const express       = require('express');
const logger        = require('morgan');
const bodyParser    = require('body-parser');
// Esta es la entrada de la aplicación.
const http = require('http');
// Configurar la aplicación con Express.
const app = express();
// Registrar las solicitudes en la consola.
app.use(logger('dev'));
// Analizar los datos de las solicitudes entrantes (https://github.com/expressjs/body-parser)
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// Configurar una ruta general predeterminada que envíe un mensaje en formato JSON.
require('./routes')(app);
app.get('*', (req, res) => res.status(200).send({
     message: 'Haz realizado una petición al servidor.',
}));
const port = parseInt(process.env.PORT, 10) || 8000;
app.set('port', port);
const server = http.createServer(app);
server.listen(port);
module.exports = app;

Ya que se ha terminado de realizar las configuraciones correspondientes se pueden realizar la pruebas a las rutas creadas por la aplicación.

Si se abre el navegador web y se usa la ruta http://localhost:8000/, se muestra la respuesta del servidor, como se ve en la siguiente imagen:

Petición a la raíz del servidor de Node.js

Si se usa la ruta http://localhost:8000/api, se muestra otra respuesta que se creó por medio de las rutas del servidor, como se muestra en la siguiente imagen:

Petición a la ruta "/api" del servidor de Node.js

Si se busca realizar acciones para trabajar con la base de datos, se va a utilizar la herramienta Postman, que soporta el manejo de peticiones de servicios REST, esto se debe a que la rutas, creadas para estas interacciones, regresan los resultados en formato JSON y no se muestran sobre una vista HTML.

Para usar la acción de obtener la lista de usuarios se utiliza la ruta localhost:8000/api/listar/usuarios con el siguiente resultado:

Resultado en formato JSON de la lista de los usuarios

Para usar la acción de obtener a un usuario en específico se utiliza la ruta localhost:8000/api/buscar/usuario/nombre/Blanca (si buscamos a "Blanca" por ejemplo) con el siguiente resultado:

Resultado en formato JSON de la búsqueda de un usuario

Para usar la acción de editar el nombre de un usuario en específico, mediante su identificador, se utiliza la ruta localhost:8000/api/actualizar/usuario/id/3/nombre/Cassandra (si se edita el usuario con el identificador número 3 de ejemplo y se visualiza la lista de usuarios de nuevo) con los siguientes resultados:

Resultado en formato JSON de la edición de un usuario

Resultado en formato JSON de la lista de los usuarios con edición de un dato

Para usar la acción de agregar un nuevo usuario, se utiliza la ruta localhost:8000/api/crear/usuario/nombre/Daniela/edad/10 (si se agrega un usuario con el nombre de "Daniela" y la edad de "10", además se visualiza la lista de usuarios de nuevo) con los siguientes resultados:

Resultado en formato JSON de la inserción de un nuevo usuario

Resultado en formato JSON de la lista de los usuarios con la inserción de un nuevo usuario

Finalmente al usar la acción de eliminar un usuario, se utiliza la ruta localhost:8000/api/borrar/usuario/id/5 (si se elimina al usuario con el identificador número 5, además se visualiza la lista de usuarios de nuevo) con los siguientes resultados:

Resultado en formato JSON de la eliminación de un usuario

Resultado en formato JSON de la lista de los usuarios con la eliminación de un usuario

Con estos ejemplos se ha revisado los diferentes servicios que se generan mediante el servidor de Node.js con Express, al fin de contar con un sistema que permite interactuar con bases de datos relacionales, en forma de posibles peticiones que haga un usuario final a este tipo de aplicaciones.

Conclusión

En esta publicación se profundizó en las técnicas disponibles para administrar y manipular bases de datos desde el entorno de Node.js, mediante la creación de un servidor y las herramientas de Express. Se manejó la idea de generar un sistema de rutas que formaran las acciones que se puede trabajar con una base de datos, con enfoque a las de tipo relacional, y se hizo un ejemplo que engloba lo visto en publicaciones pasadas del tema, como el uso de base de datos y la creación de servicios web accesibles a los usuarios. Por lo tanto, se presenta una manera de representar los datos, de la base de datos al ámbito de objetos para el lenguaje de programación JavaScript y se ofrece como una opción al momento de trabajar en la construcción de sistemas que necesitan administrar información cuando se encuentra guardada en forma de almacenamiento dedicado a sistematizar los datos.

Av. Cerro Gordo del Campestre 201
Int. 303
Col. Las Quintas
León, Gto.
C.P. 37125

ROCKTECH R+D © 2020