Usando Sequelize ORM con Node.js y Express

U

Introducción

Secuela es un ORM popular creado para Node.js, y en este tutorial lo usaremos para crear una API CRUD para administrar notas.

La interacción con bases de datos es una tarea común para las aplicaciones de back-end. Por lo general, esto se hacía a través de consultas SQL sin procesar, que pueden ser difíciles de construir, especialmente para aquellos que son nuevos en SQL o bases de datos en general.

Con el tiempo, se crearon los mapeadores relacionales de objetos (ORM), diseñados para facilitar la gestión de bases de datos. Ellos mapean automáticamente los objetos (entidades) de nuestro código en una base de datos relacional, como su nombre lo indica.

Ya no escribiríamos consultas SQL sin procesar y las ejecutaríamos en la base de datos. Al proporcionarnos una forma programática de conectar nuestro código a la base de datos y manipular los datos persistentes, podemos centrarnos más en la lógica empresarial y menos en SQL propenso a errores.

¿Qué es un ORM?

El mapeo relacional de objetos es una técnica que asigna objetos de software a tablas de bases de datos. Los desarrolladores pueden interactuar con los objetos en lugar de tener que escribir consultas a la base de datos. Cuando un objeto se lee, crea, actualiza o elimina, el ORM construye y ejecuta una consulta de base de datos bajo el capó.

Otra ventaja de los ORM es que admiten múltiples bases de datos: Postgres, MySQL, SQLite, etc. Si escribe una aplicación utilizando consultas sin formato, será difícil pasar a una base de datos diferente porque muchas de las consultas necesitarán ser reescritas.

Con un ORM, el propio ORM realiza el cambio de bases de datos y, por lo general, todo lo que necesita hacer es cambiar uno o dos valores en un archivo de configuración.

Secuela

Hay muchos ORM de node, incluidos los populares Bookshelf.js y TipoORM.

Entonces, ¿por qué y cuándo elegir Sequelize?

En primer lugar, ha existido durante mucho tiempo, 2011. Tiene miles de estrellas GitHub y es utilizado por toneladas de aplicaciones. Debido a su antigüedad y popularidad, es estable y tiene mucha documentación disponible en línea.

Además de su madurez y estabilidad, Sequelize tiene un gran conjunto de características que cubre: consultas, alcances, relaciones, transacciones, consultas sin procesar, migraciones, replicación de lectura, etc.

Una cosa a tener en cuenta es que Sequelize se basa en promesas, lo que facilita la administración de funciones asincrónicas y excepciones. También es compatible con todos los dialectos SQL populares: PostgreSQL, MySQL, MariaDB, SQLite y MSSQL.

Por otro lado, no hay soporte NoSQL que se pueda ver en ORM (o mapeadores de documentos de objetos, en este caso) como Mangosta. Realmente, decidir qué ORM elegir depende principalmente de los requisitos del proyecto en el que está trabajando.

Instalación de Sequelize

Nota: Si desea seguir el código, puede encontrarlo aquí en GitHub.

Hagamos una aplicación de node esqueleto e instalemos Sequelize. En primer lugar, creemos un directorio para nuestro proyecto, ingréselo y creemos un proyecto con la configuración predeterminada:

$ mkdir notes-app
$ cd notes-app
$ npm init -y

A continuación, crearemos el archivo de la aplicación con un enrutador y un servidor Express básico. Vamos a llamarlo index.js para que coincida con el nombre de archivo predeterminado de npm init:

A continuación, para crear fácilmente un servidor web, instalaremos Express:

$ npm install --save express

Y una vez instalado, configuremos el servidor:

const express = require('express');
const app = express();
const port = 3000;

app.get("https://Pharos.sh.com/", (req, res) => res.send('Notes App'));

app.listen(port, () => console.log(`notes-app listening on port ${port}!`));

Finalmente, podemos seguir adelante e instalar Sequelize y nuestra base de datos de elección a través de npm:

$ npm install --save sequelize
$ npm install --save sqlite3

No importa qué base de datos utilice, ya que Sequelize es independiente de la base de datos. La forma en que lo usamos es la misma, sin importar la base de datos subyacente. Es fácil trabajar con SQLite3 para el desarrollo local y es una opción popular para esos fines.

Ahora, agreguemos un código al index.js para configurar la base de datos y verificar la conexión usando Sequelize. Dependiendo de la base de datos que esté utilizando, es posible que deba definir un dialecto diferente:

const Sequelize = require('sequelize');
const sequelize = new Sequelize({
  // The `host` parameter is required for other databases
  // host: 'localhost'
  dialect: 'sqlite',
  storage: './database.sqlite'
});

Después de importar Sequelize, lo configuramos con los parámetros necesarios para su ejecución. También puede agregar más parámetros aquí, como el pool, aunque lo que tenemos es suficiente para empezar. los dialect depende de la base de datos que esté utilizando y de la storage simplemente apunta al archivo de la base de datos.

los database.sqlite El archivo se crea automáticamente en el nivel raíz de nuestro proyecto.

Nota: Vale la pena comprobar el Secuela de documentos para configurar diferentes bases de datos y la información requerida para cada una.

Si está utilizando MySQL, Postgres, MariaDB o MSSQL, en lugar de pasar cada parámetro por separado, también puede simplemente pasar el URI de conexión:

const sequelize = new Sequelize('postgres://user:[email protected]:5432/dbname');

Finalmente, probemos la conexión ejecutando el .authenticate() método. Debajo del capó, simplemente ejecuta un SELECT consulta y comprueba si la base de datos responde correctamente:

sequelize
  .authenticate()
  .then(() => {
    console.log('Connection has been established successfully.');
  })
  .catch(err => {
    console.error('Unable to connect to the database:', err);
  });

Al ejecutar la aplicación, somos recibidos con:

$ node index.js
notes-app listening on port 3000!
Executing (default): SELECT 1+1 AS result
Connection has been established successfully.

Crear un modelo para mapeo

Antes de que podamos crear una API de notas, necesitamos crear una tabla de notas. Para hacer eso, necesitamos definir un Note model, que asignaremos a una constante para que pueda usarse en toda nuestra API. En el define función especificamos el nombre de la tabla y los campos. En este caso, un campo de texto para la nota y una cadena para la etiqueta:

Al igual que con las bases de datos relacionales, antes de crear una API, primero necesitaremos crear tablas adecuadas. Como queremos evitar crearlo manualmente usando SQL, definiremos un Model class y luego haga que Sequelize lo mapee en una tabla.

Esto se puede hacer extendiendo el Sequelize.Model clase y ejecutando el .init() función, pasando parámetros, o definiendo un const y asignándole el valor devuelto del .define() método de Sequelize.

Este último es más conciso, así que iremos con ese:

const Note = sequelize.define('notes', { note: Sequelize.TEXT, tag: Sequelize.STRING });

Mapeo del modelo a la base de datos

Ahora que tenemos un Note modelo podemos crear el notes tabla en la base de datos. En una aplicación de producción, normalmente haríamos cambios en la base de datos a través de migraciones para que los cambios se rastreen en el control de fuente.

Sin embargo, para mantener las cosas concisas, usaremos el .sync() método. Que .sync() lo hace es simple: sincroniza todos los modelos definidos con la base de datos:

sequelize.sync({ force: true })
  .then(() => {
    console.log(`Database & tables created!`);
  });

Aquí, hemos utilizado el force bandera y ponerlo en true. Si ya existe una tabla, el método DROP eso y CREATE uno nuevo. Si no existe, se acaba de crear una tabla.

Finalmente, creemos algunas notas de muestra que luego persistiremos en la base de datos:

sequelize.sync({ force: true })
  .then(() => {
    console.log(`Database & tables created!`);

    Note.bulkCreate([
      { note: 'pick up some bread after work', tag: 'shopping' },
      { note: 'remember to write up meeting notes', tag: 'work' },
      { note: 'learn how to use node orm', tag: 'work' }
    ]).then(function() {
      return Note.findAll();
    }).then(function(notes) {
      console.log(notes);
    });
  });

Ejecutando el servidor, nuestras notas se imprimen en la consola, así como las operaciones SQL realizadas por Sequelize. Conéctese a la base de datos para verificar que los registros se hayan agregado correctamente:

$ sqlite3 database.sqlite
sqlite> select * from notes;
1|pick up some bread after work|shopping|2020-02-21 18:24:19.402 +00:00|2020-02-21 18:24:19.402 +00:00
2|remember to write up meeting notes|work|2020-02-21 18:24:19.402 +00:00|2020-02-21 18:24:19.402 +00:00
3|learn how to use node orm|work|2020-02-21 18:24:19.402 +00:00|2020-02-21 18:24:19.402 +00:00
sqlite> .exit

Con la base de datos en su lugar y nuestras tablas creadas, sigamos adelante e implementemos la funcionalidad básica de CRUD.

Entidades de lectura

Nuestro modelo, Note, ahora tiene métodos incorporados que nos ayudan a realizar operaciones en los registros persistentes en la base de datos.

Leer todas las entidades

Por ejemplo, podemos leer todos los registros de esa clase guardados usando el .findAll() método. Hagamos un punto final simple que sirva a todas las entidades persistentes:

app.get('/notes', function(req, res) {
  Note.findAll().then(notes => res.json(notes));
});

los .findAll() El método devuelve una matriz de notas, que podemos usar para representar un cuerpo de respuesta, a través de res.json.

Probemos el punto final a través de curl:

$ curl http://localhost:3000/notes
[{"id":1,"note":"pick up some bread after work","tag":"shopping","createdAt":"2020-02-27T17:02:10.881Z","updatedAt":"2020-02-27T17:02:10.881Z"},{"id":2,"note":"remember to write up meeting notes","tag":"work","createdAt":"2020-02-27T17:02:10.881Z","updatedAt":"2020-02-27T17:02:10.881Z"},{"id":3,"note":"learn how to use node orm","tag":"work","createdAt":"2020-02-27T17:02:10.881Z","updatedAt":"2020-02-27T17:02:10.881Z"}]

Como puede ver, todas las entradas de nuestra base de datos nos fueron devueltas, pero en formato JSON.

Sin embargo, si buscamos agregar un poco más de funcionalidad, tenemos operaciones de consulta como SELECT, WHERE, AND, ORy LIMIT apoyado por este método.

Puede encontrar una lista completa de los métodos de consulta compatibles en el Secuela de documentos página.

Leer entidades DONDE

Con eso en mente, creemos un punto final que sirva una nota única y específica:

app.get('/notes/:id', function(req, res) {
  Note.findAll({ where: { id: req.params.id } }).then(notes => res.json(notes));
});

Los puntos finales aceptan un id , utilizado para buscar una nota a través del WHERE cláusula. Probémoslo a través de curl:

$ curl http://localhost:3000/notes/2
[{"id":2,"note":"remember to write up meeting notes","tag":"work","createdAt":"2020-02-27T17:03:17.592Z","updatedAt":"2020-02-27T17:03:17.592Z"}]

Nota: Dado que esta ruta utiliza un parámetro comodín, :id, coincidirá con cualquier cadena que venga después /notes/. Por esta razón, esta ruta debe estar al final de su archivo index.js. Esto permite otras rutas, como /notes/search, para manejar una solicitud antes /notes/:id lo recoge. De lo contrario, el search La palabra clave en la ruta de la URL se tratará como una ID.

Leer entidades DONDE Y

Para consultas aún más específicas, creemos un punto final utilizando ambos WHERE y AND declaraciones:

app.get('/notes/search', function(req, res) {
  Note.findAll({ where: { note: req.query.note, tag: req.query.tag } }).then(notes => res.json(notes));
});

Aquí, buscamos notas que coincidan tanto con note y tag especificado por los parámetros. Nuevamente, probémoslo a través de curl:

$ curl "http://localhost:3000/notes/search?note=pick%20up%20some%20bread%20after%20work&tag=shopping"
[{"id":1,"note":"pick up some bread after work","tag":"shopping","createdAt":"2020-02-27T17:09:53.964Z","updatedAt":"2020-02-27T17:09:53.964Z"}]

Leer entidades O

Si intentamos ser un poco más vagos, podemos usar la OR declaración y busque notas que coincidan con cualquiera de los parámetros dados. Cambiar el /notes/search ruta a:

const Op = Sequelize.Op;

app.get('/notes/search', function(req, res) {
  Note.findAll({
    where: {
      tag: {
        [Op.or]: [].concat(req.query.tag)
      }
    }
  }).then(notes => res.json(notes));
});

Aquí estamos usando Sequelize.Op para implementar un OR consulta. Sequelize ofrece varios operadores para elegir, como Op.or, Op.and, Op.eq, Op.ne, Op.is, Op.not, etc. Estos se utilizan principalmente para crear operaciones más complejas, como consultas con una cadena de expresiones regulares.

Tenga en cuenta que estamos usando req.query.tag como argumento para .findAll(). Sequelize espera una matriz aquí, así que forzamos tag para ser una matriz usando [].concat(). En nuestra prueba a continuación, pasaremos varios argumentos en nuestra URL de solicitud:

$ curl "http://localhost:3000/notes/search?tag=shopping&tag=work"
[{"id":1,"note":"pick up some bread after work","tag":"shopping","createdAt":"2020-02-27T17:11:27.518Z","updatedAt":"2020-02-27T17:11:27.518Z"},{"id":2,"note":"remember to write up meeting notes","tag":"work","createdAt":"2020-02-27T17:11:27.518Z","updatedAt":"2020-02-27T17:11:27.518Z"},{"id":3,"note":"learn how to use node orm","tag":"work","createdAt":"2020-02-27T17:11:27.518Z","updatedAt":"2020-02-27T17:11:27.518Z"}]

Al pasar el mismo parámetro de consulta varias veces de esta manera, se mostrará como una matriz en el req.query objeto. Entonces, en el ejemplo anterior, req.query.tag es ['shopping', 'work'].

Límite de entidades de lectura

Lo último que cubriremos en esta sección es LIMIT. Digamos que queríamos modificar la consulta anterior para que solo devolviera dos resultados como máximo. Haremos esto agregando el limit parámetro y asignándole un entero positivo:

const Op = Sequelize.Op;

app.get('/notes/search', function(req, res) {
  Note.findAll({
    limit: 2,
    where: {
      tag: {
        [Op.or]: [].concat(req.query.tag)
      }
    }
  }).then(notes => res.json(notes));
});

Puede ver una lista completa de funciones de consulta en el Secuela de documentos.

Insertar entidades

Insertar entidades es mucho más sencillo ya que realmente no hay dos formas de realizar esta operación.

Agreguemos un nuevo punto final para agregar notas:

const bodyParser = require('body-parser');
app.use(bodyParser.json());

app.post('/notes', function(req, res) {
  Note.create({ note: req.body.note, tag: req.body.tag }).then(function(note) {
    res.json(note);
  });
});

los body-parser El módulo es necesario para que el punto final acepte y analice los parámetros JSON. No es necesario instalar explícitamente el body-parser paquete porque ya está incluido con Express.

Dentro de la ruta estamos usando el .create() método para insertar una nota en la base de datos, según los parámetros pasados.

Podemos probarlo con otro curl solicitud:

$ curl -d '{"note":"go the gym","tag":"health"}' -H "Content-Type: application/json" -X POST http://localhost:3000/notes
{"id":4,"note":"go the gym","tag":"health","updatedAt":"2020-02-27T17:13:42.281Z","createdAt":"2020-02-27T17:13:42.281Z"}

Ejecutar esta solicitud resultará en la creación de una nota en nuestra base de datos y nos devolverá el nuevo objeto de la base de datos.

Actualización de entidades

A veces, deseamos actualizar entidades ya existentes. Para hacer esto, confiaremos en el .update() método sobre el resultado de la .findByPk() método:

app.put('/notes/:id', function(req, res) {
  Note.findByPk(req.params.id).then(function(note) {
    note.update({
      note: req.body.note,
      tag: req.body.tag
    }).then((note) => {
      res.json(note);
    });
  });
});

los .findByPk() El método también es un método heredado en nuestra clase modelo. Busca una entidad con la clave primaria dada. Esencialmente, es más fácil devolver entidades individuales por su ID usando este método que escribiendo un SELECT WHERE consulta.

Dada la entidad devuelta, ejecutamos el .update() método para poner realmente los nuevos valores en su lugar. Verifiquemos esto a través de curl:

$ curl -X PUT -H "Content-Type: application/json" -d '{"note":"pick up some milk after work","tag":"shopping"}' http://localhost:3000/notes/1
{"id":1,"note":"pick up some milk after work","tag":"shopping","createdAt":"2020-02-27T17:14:55.621Z","updatedAt":"2020-02-27T17:14:58.230Z"}

Al disparar esta solicitud, se actualiza la primera nota con contenido nuevo y se devuelve el objeto actualizado:

Eliminar entidades

Y finalmente, cuando nos gustaría eliminar registros de nuestra base de datos, usamos el .destroy() método sobre el resultado de la .findByPk() método:

app.delete('/notes/:id', function(req, res) {
  Note.findByPk(req.params.id).then(function(note) {
    note.destroy();
  }).then((note) => {
    res.sendStatus(200);
  });
});

La ruta para .delete() parece similar a .update(). Usamos .findByPk() para encontrar una nota específica por ID. Entonces la .destroy() El método elimina la nota de la base de datos.

Finalmente, un 200 OK La respuesta se devuelve al cliente.

Conclusión

El mapeo relacional de objetos (ORM) es una técnica que asigna objetos de software a tablas de bases de datos. Sequelize es una herramienta ORM popular y estable que se utiliza junto con Node.js. En este artículo, hemos discutido qué son los ORM, cómo funcionan y cuáles son algunas de las ventajas de usarlos sobre la escritura de consultas sin formato.

Con ese conocimiento, procedimos a escribir una aplicación Node.js / Express simple que usa Sequelize para persistir Note modelo a la base de datos. Usando los métodos heredados, hemos realizado operaciones CRUD en la base de datos.

No dude en consultar el código en GitHub si tuvo algún problema para seguir este tutorial.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad