🐘🗂️ Гибкая ORM для Node.js – Sequelize
Когда начинаешь делать очередной проект, появляется желание упростить себе жизнь и лишний раз не писать SQL-запросы. В таком случае было бы неплохо познакомиться с технологией ORM.
Что такое ORM
ORM – (с англ. Object-Relational Mapping, объектно-реляционное отображение) технология в программировании, которая связывает ваши объекты с базой данных, тем самым создавая виртуальную базу данных. К виртуальной базе данных можно обращаться, извлекая или записывая информацию без написания SQL-запросов.
Что такое Sequelize
Sequelize – это Node.js ORM на базе промисов, которая может работать в связке Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift.
Sequelize может помочь закрыть 90% нужных задач без написания SQL-запросов. Внутри есть поддержка создания, обновления, удаления сущностей. Есть поддержка вложенных сортировок, сложных условий, LEFT JOIN, лимитов, подзапросов, кастомных запросов, а также есть защита от SQL-инъекций и отмена транзакций.
Установка и настройка
Как базу данных мы будем использовать PostgreSQL, поэтому пример интеграции Sequelize в проект будем показывать на ней. С вас готовый Node.js-сервер (можно с express) и развернутая база данных.
Для начала, установим Sequelize командой:
npm install sequelize
После этого устанавливаем «драйверы» для ORM:
npm install pg pg-hstore
Если вы пожелаете использовать MySQL вместо Postgres, то вам надо установить другие пакеты:
npm install --save mysql2
Подробней про это можно почитать тут.
Подключайте базу данных в основном файле проекта (это может быть app.js).
const db = require('./db.js') db.authenticate() .catch(error => console.error(error))
const Sequilize = require('sequelize') module.exports = new Sequilize('proglib', 'postgres', 'secret', { host: 'localhost', dialect: 'postgres', operatorsAliases: 0, pool: { max: 5, min: 0, acquire: 3000, idle: 10000 } })
После запуска вы должны увидеть в консоли SELECT 1+1 AS result
. Это значит, что подключение прошло успешно:
Методы
У Sequelize есть множество методов, которые позволяют удобно взаимодействовать с базой данных. Но перед тем как начать их рассматривать, нам нужно сделать некоторые подготовительные работы.
Создание таблицы в базе и ORM класса в проекте
Для начала давайте создадим таблицу users
в базе данных, а после ORM класс в Node.js для взаимодействия с ней:
Теперь нам нужно создать нужный класс модели в нашем проекте. Для этого создайте папку models
и добавьте там файл users.js
. Добавьте в файл этот код:
// Db const { DataTypes } = require('sequelize') const db = require('../db.js') const Users = db.define('users', // Описание таблиц { user_id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false }, firstname: { type: DataTypes.STRING, allowNull: false }, lastname: { type: DataTypes.STRING, allowNull: false }, comment: { type: DataTypes.TEXT, allowNull: true }, order_by: { type: DataTypes.INTEGER, allowNull: false }, file_id: { type: DataTypes.INTEGER, allowNull: true } }, // Опции { timestamps: false } ) module.exports = Users
Теперь импортируйте в нужное место и используйте по назначению.
Создание элемента
const Users = require('./models/users.js') await Users.create({ firstname: 'Иван', lastname: 'Иванов', comment: 'Классный парень', order_by: 10 })
Если какие-то поля в описании модели имеют allowNull: false
, и вы попытаетесь создать сущность без них, то фреймворк выдаст ошибку.
Обновление элемента
const Users = require('./models/users.js') await Users.update({ firstname: 'Сергей' }, { where: { user_id: 1 } })
Удаление элемента
const Users = require('./models/users.js') await Users.destroy({ where: { user_id: 1 } })
Найти один элемент
const Users = require('./models/users.js') const user = await Users.findOne({ where: { user_id: 1 } })
Найти много элементов
const Users = require('./models/users.js') const user = await Users.findAll({ where: { order_by: 10 } })
Если хотите указать лимит, то можно добавить атрибуты offset
и limit
к аргументам объекта:
const Users = require('./models/users.js') const user = await Users.findAll({ where: { order_by: 10 }, offset: 0, limit: 10 })
А если хотите получить какие-то конкретные поля (а не все), то достаточно указать аргумент attributes
и передать туда массив с нужными полями:
const Users = require('./models/users.js') const user = await Users.findAll({ attributes: ['firstname', 'lastname', 'order_by'], where: { order_by: 10 }, offset: 0, limit: 10 })
Если хотите все отсортировать, достаточно указать атрибут order
и указать, какую сортировку будем делать и по какому полю:
const Users = require('./models/users.js') const user = await Users.findAll({ attributes: ['firstname', 'lastname', 'order_by'], where: { order_by: 10 }, offset: 0, limit: 10, order: [ ['order_by', 'ASC'] ] })
Кроме ASC
(по возрастанию) можно указать DESC
(по убыванию).
Сложные условия
Для сложных условий существует оператор Op
. Он поддерживает множество конструкций, например: and
, or
, not in
, in
, like
, between
, not between
, регулярные выражения. Давайте продемонстрируем парочку примеров.
Или order_by
равно 10 или user_id
равно 1:
const { Op } = require('sequelize') const Users = require('./models/users.js') const user = await Users.findAll({ where: { [Op.or]: { order_by: 10, user_id: 1 } } })
Все. Но лишь бы не user_id
под номером 1:
const { Op } = require('sequelize') const Users = require('./models/users.js') const user = await Users.findAll({ where: { user_id: { [Op.notIn]: [1] } } })
Поиск через iLike
:
const { Op } = require('sequelize') const Users = require('./models/users.js') const user = await Users.findAll({ where: { name: { [Op.iLike]: `%Иван%` } } })
В MySQL нет оператора iLike
, надо использовать like
. Разница лишь в поиске с учетом регистра и без.
Инкремент и декремент
Прибавить 1 к полю order_by
:
const Users = require('./models/users.js') await Users.increment('order_by', { by: 1, where: { user_id: 1 } })
Убавить 1 от поля order_by
:
const Users = require('./models/users.js') await Users.decrement('order_by', { by: 1, where: { user_id: 1 } })
Кастомные запросы
const db = require('./db.js') await db.query('SELECT * FROM users')
Можно связать с моделью, сделав свой собственный метод и вызывать его через model.myMethod()
. Для этого нужно просто добавить метод в модель таким способом:
Связи
Чтобы делать LEFT JOIN
и тянуть данные из связанных таблиц полезно сделать связь. Для этого давайте создадим таблицу files
в базе данных с полями file_id
, path
. И забьем ее данными:
И не забудем добавить file_id
к нужным пользователям в таблице users
:
Теперь надо добавить связь в ORM класс:
Users.hasOne(Files, { foreignKey: 'file_id', sourceKey: 'file_id', as: 'file_info' }) Files.belongsTo(Users, { foreignKey: 'file_id', targetKey: 'file_id', as: 'file_info' })
Главное – выше не забудьте внутрь одной модели импортировать другую модель.
После этого в нужном месте делаете так:
const Users = require('./models/users.js') const Files = require('./models/files.js') const result = await Users.findOne({ include: [ { model: Files, as: 'file_info' } ], where: { user_id: 1 } })
Результат вас должен приятно удивить:
Можно делать include
с «обратной» стороны, если вы сделали belongsTo
. Это значит, что можно делать include
не только из основного класса, но и дополнительного (с кем связались через belongsTo
). В нашем случае из класса Files
. Также, кроме hasOne
есть еще hasMany
для «подгрузки» множества элементов.
Отмена транзакции
const Users = require('./models/users.js') const transaction = await Users.sequelize.transaction() const result = await Users.create({ firstname: 'Иван', lastname: 'Иванов', comment: 'Классный парень', order_by: 10 }, { transaction }) if (result.user_id > 25) { await transaction.rollback() } else { await transaction.commit() }
Показанным способом вы можете отменять транзакции в базе данных. Главное – не забывайте использовать commit()
для подтверждения транзакции и rollback()
для ее отмены.
В этой статье мы рассмотрели потрясающую ORM для Node.js – Sequelize. Мы научились:
- извлекать данные с различными условиями;
- устанавливать лимиты;
- сортировать результат;
- обновлять и удалять данные;
- писать свои запросы;
- отменять транзакции.