Objection.js — минималистичная ORM-библиотека для Node.js, которая сильно упрощает взаимодействие с базами данных и не перегружена дополнительными функциями, как sequalize или typeORM. Разбираемся, в чем ее специфика и как строить запросы с ее помощью.

Это адаптированный перевод статьи A Step Towards Simplified Querying in NodeJS из блога компании Velotio Technologies. Повествование ведется от лица автора оригинала.

Недавно я увидел на StackOverflow историю разработчика, который столкнулся с проблемой использования ORM. Этот кейс натолкнул меня на мысль написать статью о альтернативе sequelize — Objection.js, библиотеке, которая решает множество проблем ORM.

Что такое ORM

ORM или метод реляционного сопоставления объектов — решениe для сопоставления объектов в программе с таблицами базы данных. Он позволяет разработчикам взаимодействовать с объектами вместо того, чтобы писать запросы к базе данных.

ORM позволяет строить динамические запросы с помощью Query Builder и абстрагироваться от особенностей конкретной базы данных. Решения обычно поддерживают Postgres, MySQL, SQLite и другие базы данных.

Вопрос на StackOverflow касался преобразования приведенного ниже запроса в sequelize-запрос. Стоит отметить, что его писал начинающий разработчик:

Решение на sequelize выглядит так:

Учитывая простоту запроса, решение кажется сложным, и чем больше в нем новых отношений, тем оно сложнее. Такие простые запросы в sequelize описаны недостаточно подробно.

Для сравнения разберем, как решение задачи будет выглядеть на Objection.js:

Буду честен — я сам пользуюсь Objection.js, что не делает его лучшим ORM для NodeJS. Каждая библиотека имеет свою специфику, а некоторые разработчики вообще выступают против любых ORM и предлагают вручную писать SQL-запросы. Отчасти они правы. Если ваше приложение достаточно маленькое, и вы можете написать несколько функций-хелперов для выполнения запросов, то используйте обычный SQL вместо ORM.

ORM нужна, когда в приложении много таблиц с большим количеством связей, которые нужно определить, и выполнить несколько объединенных запросов.

Список самых упоминаемых ORM для NodeJS выглядит так:

  1. Sequelize

  2. Objection.js

  3. typeORM

Почему я выбрал Objection.js

  1. В основе решения — мощный конструктор SQL-запросов KNEX.JS.

  2. Библиотека позволяет выполнять запросы через функции async/await.

  3. Позволяет добавить валидацию с помощью JSON схемы.

  4. Позволяет создавать модели для таблиц с использованием синтаксиса ES6/ES7 и определять связи между ними.

  5. Objection.js автоматически строит связи между объектами и самостоятельно определяет, какие данные нужно сохранить, а какие — обновить (за это отвечают функции upsert и insert).

У Objection.js и KNEX.JS очень понятная и обширная документация, в которой легко найти инструкции и примеры использования. В официальном репозитории библиотеки на GitHub есть множество примеров использования библиотеки и подробная инструкция по ее установке и настройке.

Создание и управление схемой базы данных

Миграция — один из подходов к внесению изменений в базу данных, в этой статье сосредоточимся на нем. В Objection.js используются механизмы миграции KNEX.JS. Поскольку речь идет об ORM, определять таблицы и столбцы можно не с помощью SQL, а непосредственно написанием кода на JavaScript. Для создания новой миграции выполним команду KNEX CLI:

После выполнения этой команды в каталоге миграций появится новый файл с текущей временной меткой и именем, которое мы присвоили этой миграции. Выглядит он следующим образом:

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

Вторая функция, exports.down, выполняет противоположные первой действия: например, когда exports.up создает таблицу, exports.down удаляет ее. Она нужна для того, чтобы быстро откатить изменения, сделанные миграцией, если это потребуется.

В случае ниже в функции upсоздается таблица persons с необходимыми столбцами и индексами, а в функции down таблица удаляется, если она существует:

Запустить процесс миграции можно следующим образом:

Также можно передать флаг — env или установить NODE_ENV, чтобы выбрать альтернативную среду:

Модели

Модели базы данных — абстракции вокруг таблиц базы данных, которые помогают инкапсулировать бизнес-логику внутри них. В Objection.js модели можно создавать c помощью классов ES.

Прежде чем продолжить, уточним, что модели в Objection.js не создают таблиц в базах данных, а предназначены для добавления валидаций и создания связей между моделями. Например:

В этом коде есть три статистических геттера. Первый, tableName, возвращает имя таблицы.

Второй, jsonSchema, определяет параметры валидации каждого поля. В нем можно указать необходимые свойства: например, тип поля (число, строку, объект и тд).

Третий, relationMappings, определяет связь выбранной модели с другими моделями. В данном случае ключ внешнего объекта pets — ссылка на связанный класс. Свойство join определяет, как модели связаны друг с другом, а from и to объекта join определяют поля базы данных, с помощью которых связаны модели. Класс ModelClass является классом связанной модели.

Таким образом, класс Person имеет связь HasManyRelation (один ко многим) с моделью Animal. Модели связаны через столбцы persons.id и animals.OwneId . В результате мы видим, что у одного человека может быть несколько домашних животных.

Запросы

Начнем с простого запроса SELECT:

Простой SELECT будет выглядеть на Objection.js следующим образом.:

Запрос выше затрагивал все поля в таблице, а так можно реализовать запрос с выбором одного поля:

Из примеров выше видно, насколько запросы в Objection.js похожи на SQL-запросы. Преобразовать одни запросы в другие довольно просто, что нетипично для ORM.

Запросы создания и вставки

Другой способ его выполнения выглядит так:

Запросы удаления

Другой способ его выполнения выглядит так:

Запросы по отношениям

Предположим, мы хотим получить данные обо всех домашних животных пользователя по имени Бен:

Теперь предположим, что вы хотите добавить информацию о пользователе со всеми его животными. Для этого можно использовать запрос с помощью графа:

Доступные плагины

objection-password — плагин, который позволяет автоматически хэшировать пароли для модели Objection.js. Это легкий способ защитить пароли и другие конфиденциальные данные.

objection-graphql — автоматически генерирует GraphQL API для модели Objection.js.

Заключение

В заключение стоит отметить, что если передо мной встанет выбор, какую ORM использовать, я выберу Objection.js. В первую очередь потому, что запросы в ней выполняются проще, чем в sequalize.

В целом Objection.js не похоже на другие ORM. По сути, это просто оболочка поверх конструктора запросов KNEX.js, поэтому лучше всего воспринимать решение как конструктор запросов с дополнительными функциями.

Комментарии (10)


  1. Schekotka
    12.04.2022 11:09
    +1

    Кажется пропущен пример JS для запроса:

    SELECT * FROM persons WHERE firstName = 'Ben' ORDER BY age;

    В оригинале он такой:

    const persons = await Person.query()
      .where({ firstName: 'Ben' })
      .orderBy('age');


    1. OlegSabitov Автор
      12.04.2022 14:12

      Спасибо, действительно упустили!


  1. romadomma
    12.04.2022 11:57

    сравнительно молодая и минималистичная ORM-библиотека для Node.js

    7 лет назад первые публикации пакета появились в npm, не такая уж и молодая. На год старше того же TypeOrm. Как раз на проекте исторически сложился Objection, с приходом NestJs как раз заглядываюсь на TypeOrm.


  1. rudinandrey
    12.04.2022 12:55
    +5

    когда в приложении много таблиц с большим количеством связей, которые нужно определить, и выполнить несколько объединенных запросов

    вот по моему опыту, как раз когда в приложении много данных, по разным таблицам, и есть ложные связи, вот именно тогда лучше всего самим писать SQL запросы, потому что, что там нагенерирует эта ORM большой большой вопрос.


    1. feycot
      12.04.2022 15:50

      Ну нужно понимать, что нет серебрянной пули. Если запросы, создаваемые ORM будут мешать жить, то можно залезть и отпрофилировать и заменить на нативные запросы. Другое дело, что во время разработки зачастую важна скорость создания фичи, а оптимизацией можно заняться потом (то есть никогда :D)


  1. denbon05
    12.04.2022 14:03

    А не лучше наоборот, если приложение большое, запросы сложные и много данных, не использовать orm? Получается мы тянем в js код промежуточные данные?


    1. feycot
      12.04.2022 16:54

      А чем лучше?


      1. denbon05
        12.04.2022 22:12

        В плане производительности. К примеру мы соединяем две таблицы, при этом орм тянет в код данные из двух таблиц и потом по условию фильтрует нужные, в то время, как сырой sql запрос использует планировщик и все операции происходят на уровне бд


        1. Suvitruf
          12.04.2022 23:35

          ORM драйверу БД скармливает сформированный запрос точно такой же, как если бы вы писали его на голом SQL. Если работать с ORM правильно, то никаких лишних данных не тянется. Там есть оверхед, да, но он на уровне структур и обработки ответа.


  1. exsesx
    13.04.2022 09:22

    Prisma топ ????