🐘🗂️ Гибкая 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).

app.js
const db = require('./db.js')
db.authenticate()
  .catch(error => console.error(error))
db.js
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. И забьем ее данными:

Таблица files в базе данных.

И не забудем добавить 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
  }
})

Результат вас должен приятно удивить:

У автора стоит плагин для Google Chrome JSON Viewer

Можно делать 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. Мы научились:

  • извлекать данные с различными условиями;
  • устанавливать лимиты;
  • сортировать результат;
  • обновлять и удалять данные;
  • писать свои запросы;
  • отменять транзакции.

Материалы по теме

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

eFusion
08 января 2020

11 типов современных баз данных: краткие описания, схемы и примеры БД

Любые данные где-то хранятся. Будь это интернет вещей или пароли в *nix. По...
admin
23 февраля 2017

SQL за 20 минут

Предлагаем вашему вниманию статью с кричащим названием "SQL за 20 минут". К...