Express, parte 3: CRUD

Un CRUD de las siglas de Create, Read, Update, Delete (Crear, Leer, Modificar, Eliminar) nos permite gestionar los recursos de una aplicación.

Verbos HTTP

Hasta ahora sólo habíamos usado el método get, el cual nos permite acceder a la información. Los que vamos a ver en esta publicación son:

  • GET → Recupera recursos
  • POST → Agrega recursos
  • PUT → Modifica recursos
  • DELETE → Elimina recursos

Ordenar rutas

A medida que nuestro proyecto vaya creciendo, también lo harán las URL y nuestro archivo app.js terminará siendo caótico.

Para eso lo recomendable será ordenar nuestro código en archivos y directorios. Por ejemplo:

Helpers

Son funciones de uso habitual. Por ejemplo podemos tener un helper para obtener el formato de la fecha y hora actual. Dentro de src/helpers vamos a crear el archivo dateHelper.js:

exports.formatToday = () => {
    const date = new Date();
    return date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds()
}

Modelos

Define la lógica para gestionar la información. Dentro de src/models creamos eventoModel.js:

//Conexión a la base de datos.
const connection = require('../../db');

//Helper para fechas
const {formatToday} = require('../helpers/dateHelper')

//Devuelve la lista completa de los eventos.
exports.all = async() => {
    const query = `
        SELECT id, nombre, cupo
        FROM eventos
    `;
    try{
        [results] = await connection.query(query);
        return results;
    }catch(error){
        throw error;
    }
}

//Crea un evento nuevo.
exports.create = async( {nombre, descripcion, cupo} ) => {
    const query = `
        INSERT INTO eventos(nombre, descripcion, cupo, fecha_creacion, fecha_modificacion)
        VALUES(?, ?, ?, ?, ?)
    `;
    try{
        await connection.query(query, [nombre, descripcion, cupo, formatToday(), formatToday()]);
    }catch(error){
        throw error;
    }
}

//Recupera un evento por su ID.
exports.find = async(ID) => {
    const query = `
        SELECT id, nombre, cupo
        FROM eventos
        WHERE id = ?
    `;
    try{
        [results] = await connection.query(query, [ID]);
        return (results.length == 1) ? results[0] : null;
    }catch(error){
        throw error;
    }
}

//Modifica un evento.
exports.update = async( {ID, nombre, descripcion, cupo} ) => {
    const query = `
        UPDATE eventos
        SET
            nombre = ?,
            descripcion = ?,
            cupo = ?,
            fecha_modificacion = ?
        WHERE id = ?
    `;
    try{
        await connection.query(query, [nombre, descripcion, cupo, formatToday(), ID]);        
    }catch(error){
        throw error;
    }
}

//Elimina un evento.
exports.delete = async( ID ) => {
    const query = `
        DELETE FROM eventos
        WHERE id = ?
    `;
    try{
        await connection.query(query, [ID]);        
    }catch(error){
        throw error;
    }
}

Controladores

Define la lógica del sistema. Vamos al src/controllers y creamos eventoController.js:

const eventoModel = require('../models/eventoModel');

exports.index = async(req, res) => {
    try{
        const results = await eventoModel.all();
        res.json({ success: true, results });
    }catch(error){
        console.log(error);
        res.status(500).json({ success: false, message: 'Error al intentar recuperar los eventos' });
    }
}

exports.store = async(req, res) => {
    const {nombre, descripcion, cupo} = req.body;
    try{
        await eventoModel.create( {nombre, descripcion, cupo} );
        res.json({ success: true, message: 'El evento se ha creado correctamente'});
    }catch(error){
        console.log(error);
        res.status(500).json({ success: false, message: 'Error al intentar agregar el evento' });
    }
}

exports.show = async(req, res) => {
    const {ID} = req.params;
    try{
        const result = await eventoModel.find(ID);
        if(result == null){
            //El evento con ese ID no existe.
            res.status(404).json({ success: false, message: 'El evento no existe o ha dejado de existir' });
        }else{            
            res.json({ success: true, result });
        }
    }catch(error){
        console.log(error);
        res.status(500).json({ success: false, message: 'Error al intentar recuperar el evento' });
    }
}

exports.update = async(req, res) => {
    const { ID } = req.params;
    const { nombre, descripcion, cupo } = req.body;
    try{
        eventoModel.update( {nombre, descripcion, cupo, ID} );
        res.json({ success: true, message: 'El evento se ha modificado correctamente'});
    }catch(error){
        console.log(error);
        res.status(500).json({ success: false, message: 'Error al intentar modificar el evento' });
    }
}

exports.destroy = async(req, res) => {
    const { ID } = req.params;
    try{
        eventoModel.delete( ID );
        res.json({ success: true, message: 'El evento se ha eliminado correctamente'});
    }catch(error){
        console.log(error);
        res.status(500).json({ success: false, message: 'Error al intentar eliminar el evento' });
    }
}

Rutas

Definimos las rutas para usar los controladores. Dentro de src/routes eventoRoutes.js:

const express = require('express');
const router = express.Router();

//Controlador
const eventoController = require('../controllers/eventoController');

router.get('/eventos', eventoController.index);
router.post('/eventos', eventoController.store);
router.get('/eventos/:ID', eventoController.show);
router.put('/eventos/:ID', eventoController.update);
router.delete('/eventos/:ID', eventoController.destroy);

module.exports = router;

Agregando rutas a nuestra aplicación

Finalmente podemos eliminar las líneas de código que habíamos creado en la publicación pasada:

//Ejemplo de cómo recuperar una lista de registros.
app.get('/eventos', async (req, res) => {
  const query = `
      SELECT id, nombre, descripcion, cupo
      FROM eventos
  `;
  try{
      [results] = await connection.query(query);
      res.json({ success: true, results });
  }catch(error){
      console.log(error);
      res.status(500).json({ success: false, message: 'Error al intentar recuperar los eventos' });
  }
});


//Ejemplo de cómo recuperar un registro por su ID.
app.get('/eventos/:ID', async (req, res) => {
  const {ID} = req.params;
  const query = `
      SELECT id, nombre, descripcion, cupo
      FROM eventos
      WHERE id = ?
  `;
  try{        
      [results] = await connection.query(query, [ID]);
      if(results.length < 1){
          res.status(404).json({ success: false, message: 'El evento no existe' });
      }else{
          res.json({ success: true, result: results[0] });
      }
  }catch(error){
      console.log(error);
      res.status(500).json({ success: false, message: 'Error al intentar recuperar el evento' });
  }
});

Y reemplazarlas por:

app.use(require('./src/routes/eventoRoutes'));

Verbos HTTP desde Postman

Vamos a empezar por listar todos los eventos, algo que ya habíamos hecho en la publicación pasada:

Como podemos ver, el verbo es GET, estamos accediendo a la lista de recursos (eventos)

Verbo POST

En el caso de querer agregar un recuso nuevo, tenemos que usar el verbo POST:

Además los verbos POST suelen ir acompañados de información (body), que entre otras cosas estará oculta en la URL. En nuestro caso en particular enviamos el nombre, descripción y cupo del evento.

Verbo GET

El verbo GET tiene la particularidad que puede contener información dinámica en la URL. Por ejemplo podemos ver el evento que acabamos de crear agregando el ID del mismo:

Verbo PUT

Como ya mencioné al principio de la publicación, el verbo PUT se utiliza para modificar un recurso. Debemos pasarle por la URL el evento que queremos modificar y también la información nueva en el cuerpo de la petición:

Verbo DELETE

Y finalmente cerramos el ciclo, eliminando el recuso: