Express

Express

Tutorial Express: Introducción a ORM Sequelize

Aprende a instalar, configurar Sequelize y definir modelos básicos para gestionar bases de datos en Express con buenas prácticas y validaciones.

Aprende Express y certifícate

Instalación y configuración Sequelize

Sequelize es un ORM (Object-Relational Mapping) que nos permite trabajar con bases de datos relacionales usando JavaScript en lugar de escribir consultas SQL directamente. En el contexto de Express, Sequelize actúa como una capa de abstracción que facilita la comunicación entre nuestra aplicación y la base de datos.

Un ORM traduce los datos entre el sistema de tipos utilizado en un lenguaje de programación orientado a objetos y una base de datos relacional. Esto significa que podemos trabajar con objetos JavaScript para representar filas de tablas, y Sequelize se encarga de convertir nuestras operaciones en consultas SQL apropiadas.

Instalación de dependencias

Para comenzar a trabajar con Sequelize en nuestro proyecto Express, necesitamos instalar tanto Sequelize como el driver específico para nuestra base de datos. En este ejemplo utilizaremos PostgreSQL, aunque Sequelize también soporta MySQL, MariaDB, SQLite y Microsoft SQL Server.

npm install sequelize pg pg-hstore

El paquete pg es el driver de PostgreSQL para Node.js, mientras que pg-hstore permite trabajar con el tipo de datos hstore de PostgreSQL. Si prefieres usar otra base de datos, deberás instalar su driver correspondiente:

# Para MySQL/MariaDB
npm install mysql2

# Para SQLite
npm install sqlite3

# Para SQL Server
npm install tedious

También es recomendable instalar Sequelize CLI como dependencia de desarrollo para facilitar tareas como migraciones y generación de modelos:

npm install --save-dev sequelize-cli

Configuración inicial de Sequelize

Una vez instaladas las dependencias, necesitamos configurar la conexión a nuestra base de datos. Crea un archivo database.js en la carpeta config de tu proyecto Express:

// config/database.js
const { Sequelize } = require('sequelize');

const sequelize = new Sequelize({
  dialect: 'postgres',
  host: process.env.DB_HOST || 'localhost',
  port: process.env.DB_PORT || 5432,
  database: process.env.DB_NAME || 'mi_aplicacion',
  username: process.env.DB_USER || 'usuario',
  password: process.env.DB_PASSWORD || 'contraseña',
  logging: process.env.NODE_ENV === 'development' ? console.log : false,
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
});

module.exports = sequelize;

Esta configuración utiliza variables de entorno para mantener segura la información sensible como credenciales de base de datos. El objeto pool define la configuración del pool de conexiones, que optimiza el rendimiento al reutilizar conexiones existentes.

Configuración de variables de entorno

Crea un archivo .env en la raíz de tu proyecto para definir las variables de entorno necesarias:

# .env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mi_aplicacion_dev
DB_USER=mi_usuario
DB_PASSWORD=mi_contraseña
NODE_ENV=development

Para cargar estas variables en tu aplicación Express, instala y configura dotenv:

npm install dotenv

Luego, carga las variables al inicio de tu aplicación principal:

// app.js
require('dotenv').config();
const express = require('express');
const sequelize = require('./config/database');

const app = express();

// Middleware y rutas...

module.exports = app;

Verificación de la conexión

Es importante verificar que la conexión a la base de datos funciona correctamente. Añade esta función de verificación en tu archivo principal:

// app.js
const testConnection = async () => {
  try {
    await sequelize.authenticate();
    console.log('✅ Conexión a la base de datos establecida correctamente');
  } catch (error) {
    console.error('❌ Error al conectar con la base de datos:', error.message);
    process.exit(1);
  }
};

// Verificar conexión al iniciar la aplicación
testConnection();

Sincronización con la base de datos

Sequelize puede crear automáticamente las tablas basándose en los modelos que definamos. Para configurar la sincronización, añade el siguiente código:

// app.js
const syncDatabase = async () => {
  try {
    // En desarrollo, sincroniza los modelos con la base de datos
    if (process.env.NODE_ENV === 'development') {
      await sequelize.sync({ alter: true });
      console.log('📊 Modelos sincronizados con la base de datos');
    }
  } catch (error) {
    console.error('❌ Error al sincronizar modelos:', error.message);
  }
};

// Llamar después de verificar la conexión
testConnection().then(() => {
  syncDatabase();
});

La opción alter: true permite que Sequelize modifique las tablas existentes para que coincidan con los modelos, lo cual es útil durante el desarrollo. En producción, es recomendable usar migraciones en lugar de sincronización automática.

Estructura de archivos recomendada

Para mantener el código organizado, es recomendable seguir esta estructura de archivos:

proyecto/
├── config/
│   └── database.js
├── models/
│   └── index.js
├── routes/
├── .env
├── .gitignore
└── app.js

Crea un archivo models/index.js que centralice la configuración de Sequelize:

// models/index.js
const sequelize = require('../config/database');

// Aquí importaremos todos los modelos cuando los creemos
const models = {};

// Configurar asociaciones entre modelos
Object.keys(models).forEach(modelName => {
  if (models[modelName].associate) {
    models[modelName].associate(models);
  }
});

module.exports = {
  sequelize,
  ...models
};

Configuración para diferentes entornos

Es importante tener configuraciones específicas para desarrollo, testing y producción. Crea un archivo config/config.js:

// config/config.js
module.exports = {
  development: {
    dialect: 'postgres',
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 5432,
    database: process.env.DB_NAME || 'mi_app_dev',
    username: process.env.DB_USER || 'usuario',
    password: process.env.DB_PASSWORD || 'contraseña',
    logging: console.log
  },
  test: {
    dialect: 'postgres',
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 5432,
    database: process.env.DB_NAME_TEST || 'mi_app_test',
    username: process.env.DB_USER || 'usuario',
    password: process.env.DB_PASSWORD || 'contraseña',
    logging: false
  },
  production: {
    dialect: 'postgres',
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    database: process.env.DB_NAME,
    username: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    logging: false,
    ssl: true,
    dialectOptions: {
      ssl: {
        require: true,
        rejectUnauthorized: false
      }
    }
  }
};

Con esta configuración base, Sequelize está listo para integrarse con tu aplicación Express. La conexión está establecida, las variables de entorno configuradas, y el sistema preparado para definir modelos que representen las tablas de tu base de datos.

Modelos básicos

Los modelos en Sequelize representan las tablas de nuestra base de datos como clases JavaScript. Cada modelo define la estructura de una tabla, incluyendo sus columnas, tipos de datos, validaciones y relaciones con otros modelos. Esto nos permite trabajar con datos de forma orientada a objetos en lugar de escribir consultas SQL directamente.

Un modelo actúa como una plantilla que describe cómo se almacenan y organizan los datos. Por ejemplo, si tenemos una tabla de usuarios, el modelo User definirá qué campos tiene cada usuario (nombre, email, contraseña) y qué reglas deben cumplir estos datos.

Definición de un modelo básico

Para crear nuestro primer modelo, definamos un modelo User que represente usuarios de nuestra aplicación. Crea un archivo models/User.js:

// models/User.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');

const User = sequelize.define('User', {
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  nombre: {
    type: DataTypes.STRING(100),
    allowNull: false,
    validate: {
      notEmpty: true,
      len: [2, 100]
    }
  },
  email: {
    type: DataTypes.STRING(255),
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true
    }
  },
  edad: {
    type: DataTypes.INTEGER,
    allowNull: true,
    validate: {
      min: 0,
      max: 120
    }
  },
  activo: {
    type: DataTypes.BOOLEAN,
    defaultValue: true
  }
}, {
  tableName: 'usuarios',
  timestamps: true
});

module.exports = User;

En este modelo definimos varios tipos de datos importantes. El campo id es la clave primaria que se incrementa automáticamente. Los campos nombre y email son obligatorios (allowNull: false), mientras que edad es opcional. El campo activo tiene un valor por defecto de true.

Tipos de datos fundamentales

Sequelize ofrece múltiples tipos de datos que se mapean a los tipos SQL correspondientes:

// Ejemplos de tipos de datos comunes
const Producto = sequelize.define('Producto', {
  // Tipos de texto
  nombre: DataTypes.STRING,           // VARCHAR(255)
  descripcion: DataTypes.TEXT,        // TEXT
  codigo: DataTypes.STRING(50),       // VARCHAR(50)
  
  // Tipos numéricos
  precio: DataTypes.DECIMAL(10, 2),   // DECIMAL(10,2)
  stock: DataTypes.INTEGER,           // INTEGER
  peso: DataTypes.FLOAT,              // FLOAT
  
  // Tipos de fecha
  fechaCreacion: DataTypes.DATE,      // DATETIME
  fechaSolo: DataTypes.DATEONLY,      // DATE
  
  // Tipos booleanos y otros
  disponible: DataTypes.BOOLEAN,      // BOOLEAN
  metadatos: DataTypes.JSON          // JSON (PostgreSQL)
});

Validaciones en modelos

Las validaciones aseguran que los datos cumplan ciertas reglas antes de guardarse en la base de datos. Sequelize incluye validaciones integradas y permite crear validaciones personalizadas:

const Usuario = sequelize.define('Usuario', {
  username: {
    type: DataTypes.STRING(30),
    allowNull: false,
    unique: true,
    validate: {
      len: [3, 30],           // Longitud entre 3 y 30 caracteres
      isAlphanumeric: true,   // Solo letras y números
      notContains: ' '        // No puede contener espacios
    }
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false,
    validate: {
      len: [8, 100],          // Mínimo 8 caracteres
      is: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/  // Regex personalizada
    }
  },
  telefono: {
    type: DataTypes.STRING(15),
    validate: {
      isNumeric: true,        // Solo números
      len: [9, 15]           // Entre 9 y 15 dígitos
    }
  }
});

Configuración de timestamps

Por defecto, Sequelize añade automáticamente los campos createdAt y updatedAt a todos los modelos. Puedes personalizar este comportamiento:

const Articulo = sequelize.define('Articulo', {
  titulo: DataTypes.STRING,
  contenido: DataTypes.TEXT
}, {
  timestamps: true,           // Habilitar timestamps (por defecto)
  createdAt: 'fechaCreacion', // Personalizar nombre del campo
  updatedAt: 'fechaActualizacion',
  
  // O deshabilitar timestamps completamente
  // timestamps: false
});

Operaciones básicas con modelos

Una vez definido el modelo, podemos realizar operaciones CRUD básicas. Aquí algunos ejemplos de cómo usar el modelo en nuestras rutas Express:

// routes/users.js
const express = require('express');
const User = require('../models/User');
const router = express.Router();

// Crear un nuevo usuario
router.post('/', async (req, res) => {
  try {
    const nuevoUsuario = await User.create({
      nombre: req.body.nombre,
      email: req.body.email,
      edad: req.body.edad
    });
    
    res.status(201).json(nuevoUsuario);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Obtener todos los usuarios
router.get('/', async (req, res) => {
  try {
    const usuarios = await User.findAll();
    res.json(usuarios);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Obtener un usuario por ID
router.get('/:id', async (req, res) => {
  try {
    const usuario = await User.findByPk(req.params.id);
    
    if (!usuario) {
      return res.status(404).json({ error: 'Usuario no encontrado' });
    }
    
    res.json(usuario);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports = router;

Registro de modelos en la aplicación

Para que nuestros modelos estén disponibles en toda la aplicación, debemos registrarlos en el archivo models/index.js:

// models/index.js
const sequelize = require('../config/database');
const User = require('./User');

// Registrar todos los modelos
const models = {
  User
};

// Configurar asociaciones (para futuras relaciones)
Object.keys(models).forEach(modelName => {
  if (models[modelName].associate) {
    models[modelName].associate(models);
  }
});

module.exports = {
  sequelize,
  ...models
};

Buenas prácticas para modelos

Al definir modelos, es importante seguir ciertas convenciones que faciliten el mantenimiento del código:

  • Nombres en singular: Los modelos se nombran en singular (User, no Users), ya que representan una instancia individual.

  • Campos obligatorios: Define claramente qué campos son obligatorios usando allowNull: false.

  • Validaciones apropiadas: Incluye validaciones que reflejen las reglas de negocio de tu aplicación.

  • Índices únicos: Usa unique: true para campos que deben ser únicos como emails o usernames.

const Cliente = sequelize.define('Cliente', {
  dni: {
    type: DataTypes.STRING(9),
    allowNull: false,
    unique: true,
    validate: {
      len: [9, 9],
      isAlphanumeric: true
    }
  },
  nombre: {
    type: DataTypes.STRING(100),
    allowNull: false,
    validate: {
      notEmpty: true
    }
  },
  fechaNacimiento: {
    type: DataTypes.DATEONLY,
    validate: {
      isDate: true,
      isBefore: new Date().toISOString() // No puede ser fecha futura
    }
  }
}, {
  tableName: 'clientes',
  indexes: [
    {
      unique: true,
      fields: ['dni']
    }
  ]
});

Con estos fundamentos, ya puedes crear modelos que representen las entidades principales de tu aplicación Express. Los modelos proporcionan una interfaz limpia y segura para interactuar con la base de datos, manejando automáticamente la validación de datos y la conversión entre objetos JavaScript y registros SQL.

Aprende Express online

Otras lecciones de Express

Accede a todas las lecciones de Express y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Express y certifícate

Ejercicios de programación de Express

Evalúa tus conocimientos de esta lección Introducción a ORM Sequelize con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.