12 октября 2022

🐘🗂️ Гибкая ORM для Node.js – Sequelize

Технический директор компании vverh.digital. JavaScript программист, любитель Kotlin и Swift.
Когда начинаешь делать очередной проект, появляется желание упростить себе жизнь и лишний раз не писать SQL-запросы. В таком случае было бы неплохо познакомиться с технологией ORM.
🐘🗂️ Гибкая ORM для Node.js – Sequelize

Что такое 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. Это значит, что подключение прошло успешно:

🐘🗂️ Гибкая ORM для Node.js – Sequelize
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека фронтендера»

Методы

У Sequelize есть множество методов, которые позволяют удобно взаимодействовать с базой данных. Но перед тем как начать их рассматривать, нам нужно сделать некоторые подготовительные работы.

Создание таблицы в базе и ORM класса в проекте

Для начала давайте создадим таблицу users в базе данных, а после ORM класс в Node.js для взаимодействия с ней:

🐘🗂️ Гибкая ORM для Node.js – Sequelize

Теперь нам нужно создать нужный класс модели в нашем проекте. Для этого создайте папку 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(). Для этого нужно просто добавить метод в модель таким способом:

🐘🗂️ Гибкая ORM для Node.js – Sequelize

Связи

Чтобы делать LEFT JOIN и тянуть данные из связанных таблиц полезно сделать связь. Для этого давайте создадим таблицу files в базе данных с полями file_id, path. И забьем ее данными:

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

И не забудем добавить file_id к нужным пользователям в таблице users:

🐘🗂️ Гибкая ORM для Node.js – Sequelize

Теперь надо добавить связь в ORM класс:

🐘🗂️ Гибкая ORM для Node.js – Sequelize
        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
У автора стоит плагин для 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. Мы научились:

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

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

МЕРОПРИЯТИЯ

Комментарии

 
 

ВАКАНСИИ

Добавить вакансию
Senior DevOps Developer
Лимасол, по итогам собеседования
Senior MLE (SE)
от 5000 USD до 9000 USD
Ведущий SRE инженер
Москва, по итогам собеседования

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

Подпишись

на push-уведомления