Краткий обзор и пошаговое руководство по библиотекам ORM в JavaScript, а также первое знакомство с учебным пособием в качестве младшего разработчика.

ОРМ. Если вы разработчик, вы, вероятно, слышали об этом термине раньше. Это расшифровывается как Object Relational Mapping, но что это значит? В большинстве повседневных веб-/мобильных приложений пользовательский интерфейс должен отображать данные. Эти данные хранятся в базах данных, и для доступа к данным пишутся запросы для запроса именно тех данных, которые вы ищете. Обычно эти запросы пишутся на языке SQL. (Язык структурированных запросов, который представляет собой язык программирования для управления реляционными базами данных). Короче говоря, ORM — это метод преобразования запросов данных в экземпляры класса сущностей. Популярность использования библиотек ORM заключается в том, что вы можете получить доступ к своей базе данных без написания какого-либо SQL, что, как правило, облегчает жизнь программисту!

Экземпляры класса сущностей – это объекты, которые по существу сопоставляются со столбцами таблицы базы данных. Эти сущности могут иметь методы, выполняющие операции CRUD. (Напоминаем, что CRUD означает создание, чтение, обновление и удаление. Это относится к данным в базе данных). Эти объекты также могут выполнять пользовательские методы и запросы, такие как аутентификация пользователя или проверка полученных данных перед их отображением на интерфейс приложения.

О, так вот что такое ORM. Какие есть варианты для JavaScript?

Ниже приведены некоторые варианты популярных библиотек ORM, доступных для Javascript. Ссылки на их веб-сайт для документации и GitHub предоставляются, если вы хотите продолжить чтение.

Но прежде чем перейти к ним, важно упомянуть Knex.js: SQL Query Builder (Документация, Github). Knex.js важен, потому что многие популярные библиотеки JavaScript ORM используют Knex.js под капотом вместе со своими дополнительными функциями.

Вот несколько быстрых примеров, взятых из документации веб-сайта Knex.js, чтобы ознакомиться с Knex.js.

Установка с помощью Node.js

$ npm install knex --save

# Then add one of the following (adding a --save) flag:
$ npm install pg
$ npm install pg-native
$ npm install sqlite3
$ npm install mysql
$ npm install mysql2
$ npm install oracledb
$ npm install tedious

Создание базовой таблицы с идентификатором, именем в виде строки и отметками времени для целей переноса

knex.schema.createTable('users', function (table) {
  table.increments();
  table.string('name');
  table.timestamps();
})
Outputs:
create table `users` (`id` int unsigned not null auto_increment primary key, `name` varchar(255), `created_at` datetime, `updated_at` datetime)

Соединение таблиц, очень распространенная задача при написании операторов SQL

knex('users')
  .join('contacts', 'users.id', '=', 'contacts.user_id')
  .select('users.id', 'contacts.phone')
Outputs:
select `users`.`id`, `contacts`.`phone` from `users` inner join `contacts` on `users`.`id` = `contacts`.`user_id`

И простой запрос, такой как поиск идентификатора из пользователей таблицы, поиск определенного имени и фамилии.

knex('users').where({
  first_name: 'Test',
  last_name:  'User'
}).select('id')
Outputs:
select `id` from `users` where `first_name` = 'Test' and `last_name` = 'User'

Популярные библиотеки JavaScript ORM

  1. Sequlize (Документация, GitHub, Базы данных: Postgres, MySQL, MariaDB, SQLite и Microsoft SQL Server)
  2. Книжная полка(Документация, GitHub, Базы данных: PostgreSQL, MySQL и SQLite3)
  3. Водоканал(Документация, GitHub, Базы данных: локальный диск/память, MySQL, MongoDB и Postgres (официальные адаптеры)
  4. Objection.js(Документация, GitHub, Базы данных: SQLite3, Postgres и MySQL (и все базы данных, поддерживаемые Knex.js)
  5. Mongoose(Документация, GitHub, Базы данных: MongoDB)

Вау! Так много разных ORM на выбор! Каковы плюсы и минусы каждого? Что мне выбрать?

У Sequlize отличная документация, но она очень старая и содержит нерешенные проблемы. Bookshelfиспользует Knex.js, который добавляет дополнительную документацию, чтобы понять, как использовать, но по-прежнему является отличной базовой библиотекой ORM, которая предоставляет такие функции, как полиморфные ассоциации и поддержку связи «один к одному», «один ко многим» и «многие ко многим». Waterline отлично подходит, если вы используете Sail.js в качестве платформы Node.js, так как это ORM по умолчанию, но в нем отсутствует документация по сложным запросам. Objection.js имеет отличную документацию, и даже несмотря на то, что он построен на Knex.js, синтаксис чистый и минималистичный, но не поддерживает базы данных NoSQL. Наконец, Mongoose — это очевидный выбор, если вы используете MongoDB, а также самая популярная ORM при использовании Node.js.

Сегодня я решил рассказать и поделиться своим опытом в руководстве по Objection.js. Я выбрал этот из-за его минималистического синтаксиса и отличной документации. (Я бы выбрал Mongoose, но уже есть много руководств по MongoDB и Mongoose, поэтому я подумал, что этот может быть более интересным.)

Пошаговое руководство

Я решил выполнить шаги, показанные в этом обучающем видео для Objection.js на YouTube. Я расскажу о шагах, показанных в видео, и попутно поделюсь фрагментами кода.

Настройка проекта: во-первых, вы хотите создать каталог для этого руководства и установить зависимости, необходимые для запуска Objection.js.

$ npm init -y
---------------------
$ npm install objection knex express pg
$ npm install --save-dev nodemon

Создайте файл index.js и каталог, в котором будут выполняться наши миграции и исходные файлы. Создайте knexfile.js с помощью команды npx, чтобы создать базовый файл конфигурации.

$ touch index.js
---------------------
$ mkdir db
$ cd db
$ npx knex migrate:make init
$ mkdir seeds
$ npx knex init

Добавьте конфигурацию средства отображения змеиных случаев в knexfile.js, которая в основном сопоставляет верблюжий случай (лучшая практика для JavaScript) со змеиным случаем в базе данных, что сэкономит много времени.

//objection_tutorial/db/knexfile.js
const { knexSnakeCaseMappers } = require("objection");
module.exports = {
  development: {
   // Create your database and fill in postgres database info
   -------------
   seed: {
     directory: "./seeds"
   },
   ...knexSnakeCaseMappers,
 }
}

Перейдите к файлу миграции, который мы создали на предыдущем шаге. Там видно, что есть 2 функции: exports.up и exports.down. В контексте миграции exports.up применит изменения, а export.down отменит их. Ниже показано, как создать 3 таблицы (пользователь, канал и видео), и, как вы можете видеть, Knex.js позволяет легко понять, что такое схема таблиц и как таблицы связаны.

//objection_tutorial/db/migrations/YOUR_DATE_init.js
exports.up = function (knex) {
  return knex.schema
   .createTable("channel", (table) => {
     table.increments();
     table.string("name").notNullable();
     table.timestamps(true, true);
   })
   .createTable("user", (table) => {
     table.increments();
     table.string("name").notNullable();
     table.string("email").notNullable().unique();
     table.integer("channelId").references("id").inTable("channel");
     table.timestamps(true, true);
   })
   .createTable("video", (table) => {
     table.increments();
     table.string("title").notNullable();
     table
      .integer("channelId")
      .notNullable()
      .references("id")
      .inTable("channel");
     table.timestamps(true, true);
   });
};
exports.down = function (knex) {
  return knex.schema
   .dropTableIfExists("video")
   .dropTableIfExists("user")
   .dropTableIfExists("channel");
};

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

// You must create this file, or run the command
$ npx knex seed:make dev --knexfile ./db/knexfile.js
// objection_tutorial/db/seeds/dev.js
exports.seed = async function (knex) {
  //truncate all exisings tables
  await knex.raw('TRUNCATE TABLE "user" CASCADE');
  await knex.raw('TRUNCATE TABLE "channel" CASCADE');
  await knex.raw('TRUNCATE TABLE "video" CASCADE');
  await knex("channel").insert([
    { id: 1, name: "channel1" },
    { id: 2, name: "channel2" },
  ]);
  await knex("user").insert([
    { id: 1, name: "user1", email: "[email protected]", channelId: 1 }
    { id: 2, name: "user2", email: "[email protected]", channelId: 2 }
    { id: 3, name: "user3", email: "[email protected]" },
  ]);
  return knex("video").insert([
    { id: 1, title: "video1ByUser1", channelId: 1 },
    { id: 2, title: "video2ByUser1", channelId: 1 },
    { id: 3, title: "video3ByUser2", channelId: 2 },
  ]);
};

Не забудьте запустить миграцию и начальные файлы с помощью следующих команд терминала (я добавил их как пользовательские сценарии в наш package.json).

//package.json
...
"scripts": {
  "migrate": "npx knex migrate:latest --knexfile ./db/knexfile.js",
  "seed": "npx knex seed:run --knexfile ./db/knexfile.js",
  ...
}
...
--------------------------------------------
$ npm run migrate
$ npm run seed

Точно так же создание таблиц и добавление начальных значений в созданную вами схему очень просто в JavaScript. Не было написано ни одной строки кода SQL, но если вы войдете в свой psql в своем терминале, вы увидите, что таблицы и данные добавляются в таблицы. Далее, как я могу получить доступ к данным в моем приложении Express? Прежде чем мы туда доберемся, мы должны создать экземпляры классов сущностей для каждой таблицы, упомянутой в начале статьи. Создайте папку моделей внутри каталога db и создайте 3 файла для каждой таблицы: user.js, video.js. и канал.js. Ниже приведен фрагмент того, как должна быть создана модель канала. (Повторите то же самое для моделей пользователя и видео.) Чтобы добавить отношения между идентификаторами и первичными ключами, следуйте примеру, приведенному в документации для моделей на веб-сайте Objection.js.

//objection_tutorial/db/models/channel.js
const { Model } = require("objection");
class Channel extends Model {
  static get tableName() {
    return "channel";
  }
  static get relationMappings() {
    const User = require("./user");
    return {
     user: {
      relation: Model.HasOneRelation,
      modelClass: User,
     join: {
      from: "channel.id",
      to: "user.channelId",
     },
   },
  };
 }
}
module.exports = Channel;
//repeat the same for other models/tables created 

Последний установочный файл, который нам нужен, — это вспомогательная функция для предоставления экземпляра Knex возражающим моделям. Это позволит нам использовать функции возражения для выполнения операций CRUD и запросов в нашем приложении Express (index.js).

//objection_tutorial/db/db-setup.js
const knex = require("knex");
const knexfile = require("./knexfile");
const { Model } = require("objection");
function setupDb() {
  const db = knex(knexfile.development);
  Model.knex(db);
}
module.exports = setupDb;

Наконец, в нашем index.jsфайле создайте базовое приложение Express, и мы сможем связать наши модели и отобразить данные из базы данных в нашем браузере. Все, что вам нужно сделать, это запросить модель и наш файл db-setup.js.

//objection_tutorial/index.js
const dbSetup = require("./db/db-setup");
const express = require("express");
const User = require("./db/models/user");
dbSetup();
const app = express();
app.use(express.json());
app.get("/user/:id", async (req, res) => {
  const { id } = req.params;
  //Here is where you write all the complex queries you want!
  const user = await User.query().findById(id);
  //For simplicity, we are going to return the data as a json object instead of passinng template vars through a view template and so on
  res.json(user);
});
app.listen(8000, () => console.log("Server Running on Port 8000"));

Вот и все. Когда сервер запущен, когда вы переходите к своему localhost:8000/user/1 или к любому другому месту, где у вас работает сервер, вы можете видеть, что элемент JSON для запрошенных нами данных отображается и доступен для использовать. Дополнительную информацию см. в Документация по Knex.js и Документация по Objection.js.

Заключение и заключительные мысли

В целом, библиотеки ORM чрезвычайно полезны и имеют свои преимущества при работе с огромным количеством данных в вашей базе данных. Есть много вариантов, и у каждого есть свои преимущества и свое место в современном мире Node.js. Мой опыт работы с документацией Objection.js и Knex.js показал преимущества чистого и простого синтаксиса для запроса данных, создания таблиц и исходных таблиц, которые являются общими. SQL-задачи. Как человек, который плохо знаком с ORM и ищет библиотеку с отличной документацией, я бы порекомендовал эту библиотеку всем, кто ищет ORM на JavaScript.