9 лучших ORM для JavaScript и TypeScript на 2021 год

ORM для JavaScript Программирование и разработка

В этой статье будет кратко объяснено, что такое объектно-реляционное сопоставление (ORM), что такое библиотека ORM и почему вам следует рассмотреть возможность ее использования в своем следующем проекте JavaScript. Мы также поможем вам оценить лучшие библиотеки ORM для JavaScript и TypeScript, исходя из ваших потребностей как разработчика и сопровождающего проекта.

Мы рассмотрим каждый из следующих инструментов:

  • Knex.js: SQL Query Builder
  • Sequelize
  • Bookshelf
  • Waterline
  • Objection.js
  • Mongoose
  • Typegoose
  • TypeORM
  • MikroORM
  • Prisma

Объектно-реляционное сопоставление

Объектно-реляционное сопоставление может показаться сложным, но его цель — облегчить вашу жизнь как программиста. Чтобы получить данные из базы данных, вам нужно написать запрос. Означает ли это, что вам нужно изучать SQL? Ну нет. Объектно-реляционное сопоставление позволяет писать запросы на любом языке по вашему выбору.

Реляционное сопоставление объектов — это метод преобразования результата запроса к базе данных в экземпляры класса сущности. Объект просто объект обертка для таблицы базы данных. Он содержит атрибуты, сопоставленные столбцам таблицы базы данных. Экземпляры сущностей имеют способы выполнения операций CRUD и поддерживают дополнительные функции, содержащие настраиваемую логику, такую ​​как проверка и шифрование данных.

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

Классы сущностей — это строительные блоки бизнес-приложений, поскольку они предназначены для инкапсуляции логики для реализации бизнес-правил. Бизнес-правило определяется для обеспечения того, чтобы автоматизированный процесс выполнялся только в рамках бизнес-политики. Примеры бизнес-правил включают:

  • скидки для клиентов
  • утверждения ссуд
  • комиссионные с продаж
  • расчеты по доставке и налогам
Читайте также:  Java или C++ - что выбрать

Библиотеки ORM

Реляционное отображение объектов обычно выполняется с помощью библиотеки. Термин ORM чаще всего относится к реальной библиотеке ORM — объектному реляционному преобразователю, который выполняет за вас работу по объектному реляционному отображению.

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

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

  • построители запросов
  • сценарии миграции
  • инструмент CLI для генерации шаблонного кода
  • функция заполнения для предварительного заполнения таблиц тестовыми данными

В этой статье я расскажу, как работает каждая библиотека ORM:

  • первоначальная настройка и настройка
  • основные операции CRUD
  • предварительные операции запроса

Я также включил важную информацию, такую ​​как даты запуска, количество пользователей и ссылки на документацию, а также каналы поддержки, если таковые имеются. Я также буду обсуждать важные вопросы, касающиеся производительности запросов, обслуживания библиотек и философии архитектуры, на которые вы должны серьезно повлиять при принятии решения.

А также  заказал список по дате запуска от самого раннего до самого нового. Я разделил список на два раздела в зависимости от основного поддерживаемого языка: JavaScript и TypeScript.

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

Knex.js: построитель SQL-запросов

  • Дата запуска: декабрь 2012 г.
  • GitHub: используется 158,6 тыс.
  • Базы данных: Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle и Amazon Redshift

Knex.js в настоящее время является наиболее зрелым конструктором SQL-запросов JavaScript, который может работать как в Node.js, так и в браузере (через webpack или Browserify). Он способен генерировать высокопроизводительные SQL-запросы, которые не уступают написанным вручную операторам SQL.

Так что же такое конструктор запросов?

Это просто API, который предоставляет набор функций, которые можно объединить в цепочку для формирования запроса. Вот пример:

knex({ a: 'table', b: 'table' })
  .select({
    aTitle: 'a.title',
    bTitle: 'b.title'
  })
  .whereRaw('?? = ??', ['a.column_1', 'b.column_2'])

SQL Output:
select `a`.`title` as `aTitle`, `b`.`title` as `bTitle` from `table`
as `a`, `table` as `b` where `a`.`column_1` = `b`.`column_2`

Возникает вопрос, почему следует использовать построитель запросов вместо написания необработанных операторов SQL. Я назову вам четыре причины:

  • Это помогает вам абстрагироваться от вашего кода из диалекта SQL вашей базы данных, облегчая переключение.
  • Это устраняет или значительно снижает вероятность атак SQL-инъекций на ваше приложение.
  • Это позволяет легко создавать запросы с динамическими условиями.
  • Он поставляется с дополнительными функциями и инструментами CLI для выполнения операций по разработке базы данных.

Эти функции включают:

  • пул соединений
  • обратный вызов и интерфейсы обещаний
  • потоковый интерфейс
  • сопровождение сделки
  • поддержка схемы
  • миграция
  • посев

Для его установки в приложение необходимо установить пакет Knex.js вместе с драйвером используемой базы данных:

$ npm install knex --save

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

Вот пример кода настройки:

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host : '127.0.0.1',
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  }
});

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)

Вот пример базового запроса:

knex('users').where({
  first_name: 'Test',
  last_name:  'User'
}).select('id')

Outputs:
select `id` from `users` where `first_name` = 'Test' and `last_name` = 'User'

Также поддерживаются необработанные операторы SQL. Вот пример сложного запроса:

const subcolumn = knex.raw('select avg(salary) from employee where dept_no = e.dept_no')
.wrap('(', ') avg_sal_dept');

knex.select('e.lastname', 'e.salary', subcolumn)
.from('employee as e')
.whereRaw('dept_no = e.dept_no')

Outputs:
select `e`.`lastname`, `e`.`salary`, (select avg(salary) from employee where dept_no = e.dept_no)
avg_sal_dept from `employee` as `e` where dept_no = e.dept_no

Knex.js также поддерживает TypeScript, что здорово, поскольку позволяет писать такой код:

import { Knex, knex } from 'knex'

interface User {
  id: number;
  age: number;
  name: string;
  active: boolean;
  departmentId: number;
}

const config: Knex.Config = {
  client: 'sqlite3',
  connection: {
    filename: './data.db',
  },
});

const knexInstance = knex(config);

try {
  const users = await knex<User>('users').select('id', 'age');
} catch (err) {
  // error handling
}

В приведенном выше примере TypeScript Knex.js работает почти как ORM. Однако экземпляры объекта сущности не создаются. Вместо этого определение интерфейса используется для создания объектов JavaScript с типобезопасными свойствами.

Обратите внимание, что ряд библиотек ORM, перечисленных в этой статье, используют Knex.js под капотом. Они включают:

  • Bookshelf
  • Objection.js
  • MikroORM

Библиотеки ORM часто предоставляют дополнительные функции поверх Knex.js. Давайте посмотрим на них в следующем разделе.

Библиотеки ORM JavaScript

В этой категории все перечисленные здесь библиотеки написаны на JavaScript и могут работать непосредственно в Node.js. Поддержка TypeScript предоставляется либо через встроенные типы, либо через пакет определений @ types / node. Если вам нужна первоклассная поддержка проектов TypeScript, вам следует перейти к разделу » Библиотеки ORM TypeScript».

На уровне доступа к данным используются два популярных архитектурных шаблона:

  • Data Mapper
  • Active Record

В шаблоне Data Mapper классы сущностей являются чистыми и содержат только атрибуты. Операции CRUD и бизнес-правила реализованы в контейнерах, известных как репозитории. Вот пример:

const repository = connection.getRepository(User);.

const user = new User();
user.firstName = "Timber";
await repository.save(user);

const allUsers = await repository.find();

При использовании шаблона активной записи логика для операций CRUD и бизнес-правил реализуется в классах сущностей. Вот аналогичный пример реализации вышеизложенного:

const user = new User();
user.firstName = "Timber";
await user.save();

const allUsers = await User.find();

У использования любого шаблона есть свои плюсы и минусы. Эти шаблоны были названы Мартином Фаулером в его книге » Шаблоны архитектуры корпоративных приложений» в 2003 году. Вам следует проверить книгу, если вы хотите получить более подробную информацию по этому вопросу. Большинство библиотек ORM, перечисленных в этой статье, поддерживают один или оба шаблона.

Sequelize

  • Launch: July 2010
  • GitHub: used by 271k
  • Slack
  • Databases: Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server

Sequelize — очень зрелая и популярная ORM-библиотека Node.js с отличной документацией, содержащей хорошо объясненные примеры кода. Он поддерживает многие функции уровня данных, которые мы уже упоминали ранее в предыдущих библиотеках. В отличие от Книжной полки, у него есть собственный Конструктор запросов, который работает так же хорошо, как Knex.js.

Установить библиотеку довольно просто, а драйвер базы данных довольно прост:

$ npm i sequelize # This will install v6

# And one of the following:
$ npm i pg pg-hstore # Postgres
$ npm i mysql2
$ npm i mariadb
$ npm i sqlite3
$ npm i tedious # Microsoft SQL Server

Ниже приведен пример кода установки вместе с примерами CRUD и базовых операторов запроса:

const { Sequelize } = require('sequelize');

// Connect to database
const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */
});

// Create Model
const User = sequelize.define('User', {
  // Model attributes are defined here
  firstName: {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName: {
    type: DataTypes.STRING
    // allowNull defaults to true
  }
}, {
  // Other model options go here
});

// Create instance
const jane = User.build({ firstName: "Jane", lastName: "Doe" });
await jane.save(); // save to database

// Shortcut for creating instance and saving to database at once
const jane = await User.create({ firstName: "Jane", lastName: "Doe" });

// Find all users
const users = await User.findAll();
console.log(users.every(user => user instanceof User)); // true
console.log("All users:", JSON.stringify(users, null, 2));

Ниже приводится пример написания сложного запроса:

// What if you wanted to obtain something like WHERE char_length("content") = 7?
Post.findAll({
  where: sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7)
});
// SELECT ... FROM "posts" AS "post" WHERE char_length("content") = 7

// A more complex example
Post.findAll({
  where: {
    [Op.or]: [
      sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7),
      {
        content: {
          [Op.like]: 'Hello%'
        }
      },
      {
        [Op.and]: [
          { status: 'draft' },
          sequelize.where(sequelize.fn('char_length', sequelize.col('content')), {
            [Op.gt]: 10
          })
        ]
      }
    ]
  }
});

В последнем примере сложного запроса результат SQL был:

SELECT
  ...
FROM "posts" AS "post"
WHERE (
  char_length("content") = 7
  OR
  "post"."content" LIKE 'Hello%'
  OR (
    "post"."status" = 'draft'
    AND
    char_length("content") > 10
  )
)

Sequelize поддерживает необработанные операторы SQL, что дает разработчикам гибкость при написании сложных и высокопроизводительных операторов SQL. Результаты также могут быть сопоставлены с экземплярами сущности объекта. Вот пример:

// Callee is the model definition. This allows you to easily map a query to a predefined model
const projects = await sequelize.query('SELECT * FROM projects', {
  model: Projects,
  mapToModel: true // pass true here if you have any mapped fields
});
// Each element of `projects` is now an instance of Project

Основным недостатком Sequelize является то, что разработка замедлилась, и проблемы накапливались, но не решены. К счастью, один из сопровождающих объявил, что с 2021 года библиотека получит должное внимание. Обратите внимание, что все проекты библиотеки ORM в этой статье имеют открытый исходный код и им действительно нужна помощь разработчиков, чтобы сделать их лучше.

Bookshelf

  • Launch: March, 2013
  • GitHub: Used by 22.4k
  • Plugins
  • Databases : PostgreSQL, MySQL, and SQLite3

Книжная полка — одна из старейших и самых простых библиотек ORM JavaScript, которые у нас есть. Он построен на основе Knex.js SQL Query Builder и использует множество идей из шаблона Data Mapper. Он предоставляет дополнительные функции, такие как:

  • загрузка отношений нетерпеливого и вложенного-нетерпеливого
  • полиморфные ассоциации
  • поддержка отношений «один к одному», «один ко многим» и «многие ко многим».

К сожалению, нет встроенной поддержки проверки. Однако это может быть реализовано в коде через стороннюю библиотеку, такую ​​как checkit.

Установка Bookshelf в ваш проект выглядит следующим образом:

$ npm install knex
$ npm install bookshelf

# Then add one of the following:
$ npm install pg
$ npm install mysql
$ npm install sqlite3

Код установки выглядит так:

// Setting up the database connection
const knex = require('knex')({
  client: 'mysql',
  connection: {
    host     : '127.0.0.1',
    user     : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test',
    charset  : 'utf8'
  }
})
const bookshelf = require('bookshelf')(knex)

// Define User model
const User = bookshelf.model('User', {
  tableName: 'users',
  posts() {
    return this.hasMany(Posts)
  }
})

// Define Post model
const Post = bookshelf.model('Post', {
  tableName: 'posts',
  tags() {
    return this.belongsToMany(Tag)
  }
})

// Define Tag model
const Tag = bookshelf.model('Tag', {
  tableName: 'tags'
})

// Unfortunate example of unreadable code
new User({id: 1}).fetch({withRelated: ['posts.tags']}).then((user) => {
  console.log(user.related('posts').toJSON())
}).catch((error) => {
  console.error(error)
})

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

Интересно, что Strapi, автономная CMS, использует Книжную полку в качестве коннектора базы данных по умолчанию. Однако стоит отметить следующие моменты:

документация не особо полезна
на момент написания библиотека не обновлялась пять месяцев

Waterline

  • Launch: May 2013
  • GitHub: Used by 8.5k
  • Documentation
  • Databases : Local disk/memory, MySQL, MongoDB, and Postgres(official adapters)
  • Community Database Adapters: Oracle, SAP, Cassandra, IBM, Apache Derby, Redis, Solr and more

Waterline — это ORM по умолчанию, используемый Sails.js, фреймворком Node.js. При использовании Sails.js для разработки вашего проекта объем кода, который вам нужно написать для создания собственного API базы данных, значительно сокращается. Это достигается с помощью философии «соглашение над конфигурацией» и API Blueprints, который содержит шаблонный код для доступа к базе данных и выполнения функций CRUD. Кроме того, Sails.js предоставляет интерфейс командной строки, который помогает разработчикам генерировать маршруты API, выполнять миграции и другие функции уровня данных. Поддержка машинописного текста доступна через пакет типизированных определений.

В этой статье мы предполагаем, что вы хотите использовать ORM Waterline как отдельную программу, что вполне возможно. Давайте посмотрим, как его установить и настроить.

Для установки необходимо установить библиотеку Waterline, а затем один из адаптеров базы данных:

$ npm install --save waterline

# Install database adapters
$ npm install --save sails-mysql
$ npm install --save-dev sails-disk

Вот частичный образец кода установки:

const Waterline = require('waterline');
const sailsDiskAdapter = require('sails-disk');
const waterline = new Waterline();

const userCollection = Waterline.Collection.extend({
  identity: 'user',
  datastore: 'default',
  primaryKey: 'id',

  attributes: {
    id: {
        type: 'number',
        autoMigrations: {autoIncrement: true}
    },
    firstName: {type:'string'},
    lastName: {type:'string'},

    // Add a reference to Pets
    pets: {
      collection: 'pet',
      via: 'owner'
    }
  }
});

waterline.registerModel(userCollection);

Вот частичный образец некоторого кода CRUD:

(async ()=>{
    // First we create a user
    var user = await User.create({
      firstName: 'Neil',
      lastName: 'Armstrong'
    });

    // Then we create the pet
    var pet = await Pet.create({
      breed: 'beagle',
      type: 'dog',
      name: 'Astro',
      owner: user.id
    });

    // Then we grab all users and their pets
    var users = await User.find().populate('pets');
  })()

Вот пример базового кода запроса:

var thirdPageOfRecentPeopleNamedMary = await Model.find({
  where: { name: 'mary' },
  skip: 20,
  limit: 10,
  sort: 'createdAt DESC'
});

Когда дело доходит до обработки сложных запросов, кажется, что в документации отсутствует эта часть. Если вы планируете использовать Sails.js, использование Waterline ORM не составит труда. Но как отдельная библиотека ORM сталкивается со следующими проблемами:

Objection.js

  • Launch: April 2015
  • GitHub: Used by 5.7k
  • Plugins
  • Databases : SQLite3, Postgres and MySQL (including all Knex.js supported databases)

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

Документация Objection.js отличная. Он хорошо написан, так как вы можете легко найти четкие инструкции по созданию уровня данных вашего приложения. Синтаксис чистый и легкий для понимания. Он построен на основе Knex.js и имеет официальную встроенную поддержку TypeScript. В нем есть все, что вам нужно в ORM.

Глядя на цифры, довольно удивительно, что Objection.js не так популярен, как должен быть. Библиотеки ORM, такие как Sequelize и TypeORM, предлагают гораздо больше функций, что может объяснить их популярность. Однако я думаю, что набор функций, которые решила использовать команда Objection.js, идеально подходит для библиотеки с открытым исходным кодом. Это означает, что со временем возникает меньше ошибок, и небольшая команда может своевременно их устранять. Вы можете убедиться в этом, посмотрев на вкладку » Проблемы «, на момент написания которой было около 50 нерешенных проблем.

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

Если у вас возникли какие — либо сомнения по поводу выбора этой библиотеки, проверить это можете прочитать отзывы ссылку.

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

npm install objection knex

# Install database adapter
npm install pg
npm install sqlite3
npm install mysql
npm install mysql2

Код настройки настолько прост, что не требует объяснений:

const { Model } = require('objection');
const Knex = require('knex');

// Initialize knex.
const knex = Knex({
  client: 'sqlite3',
  useNullAsDefault: true,
  connection: {
    filename: 'example.db'
  }
});

// Give the Knex instance to Objection.
Model.knex(knex);

// Person model.
class Person extends Model {
  static get tableName() {
    return 'persons';
  }

  static get relationMappings() {
    return {
      children: {
        relation: Model.HasManyRelation,
        modelClass: Person,
        join: {
          from: 'persons.id',
          to: 'persons.parentId'
        }
      }
    };
  }
}

async function createSchema() {
  if (await knex.schema.hasTable('persons')) {
    return;
  }

  // Create database schema. You should use Knex migration files
  // to do this. We create it here for simplicity.
  await knex.schema.createTable('persons', table => {
    table.increments('id').primary();
    table.integer('parentId').references('persons.id');
    table.string('firstName');
  });
}

async function main() {
  // Create some people.
  const sylvester = await Person.query().insertGraph({
    firstName: 'Sylvester',

    children: [
      {
        firstName: 'Sage'
      },
      {
        firstName: 'Sophia'
      }
    ]
  });

  console.log('created:', sylvester);

  // Fetch all people named Sylvester and sort them by ID.
  // Load `children` relation eagerly.
  const sylvesters = await Person.query()
    .where('firstName', 'Sylvester')
    .withGraphFetched('children')
    .orderBy('id');

  console.log('sylvesters:', sylvesters);
}

createSchema()
  .then(() => main())
  .then(() => knex.destroy())
  .catch(err => {
    console.error(err);
    return knex.destroy();
  });

Вот пример базового запроса:

// query 1
const person = await Person.query().findById(1);

//query 2
const middleAgedJennifers = await Person.query()
  .select('age', 'firstName', 'lastName')
  .where('age', '>', 40)
  .where('age', '<', 60)
  .where('firstName', 'Jennifer')
  .orderBy('lastName');

Вывод SQL для базового запроса:

-- query 1
select "persons".* from "persons" where "persons"."id" = 1

-- query 2
select "age", "firstName", "lastName"
from "persons"
where "age" > 40
and "age" < 60
and "firstName" = 'Jennifer'
order by "lastName" asc

Вот пример сложного запроса:

const people = await Person.query()
  .select('persons.*', 'parent.firstName as parentFirstName')
  .innerJoin('persons as parent', 'persons.parentId', 'parent.id')
  .where('persons.age', '<', Person.query().avg('persons.age'))
  .whereExists(
    Animal.query()
      .select(1)
      .whereColumn('persons.id', 'animals.ownerId')
  )
  .orderBy('persons.lastName');

console.log(people[0].parentFirstName);

Вывод SQL для сложного запроса:

select "persons".*, "parent"."firstName" as "parentFirstName"
from "persons"
inner join "persons"
  as "parent"
  on "persons"."parentId" = "parent"."id"
where "persons"."age" < (
  select avg("persons"."age")
  from "persons"
)
and exists (
  select 1
  from "animals"
  where "persons"."id" = "animals"."ownerId"
)
order by "persons"."lastName" asc

В дополнение к функциям, которые уже предоставляет Knex.js, Objection.js имеет:

  • официальная поддержка TypeScript
  • поддержка хуков жизненного цикла
  • встроенная поддержка проверки с использованием синтаксиса схемы JSON
  • плагины

Библиотека в очень хорошем состоянии. Для баз данных SQL Objection.js кажется лучшей библиотекой ORM для вашего приложения JavaScript. К сожалению, он не поддерживает базы данных NoSQL. Но следующая библиотека, которую мы представим, поддерживает базы данных NoSQL.

Mongoose

  • Launch: April 2010
  • GitHub: Used by 1.4m
  • Slack
  • Plugins
  • Databases : MongoDB

Если вы планируете использовать MongoDB в качестве базы данных, то Mongoose, вероятно, станет вашим предпочтительным ORM. В настоящее время это самая популярная библиотека ORM в мире Node.js. Mongoose использует синтаксис схемы для определения моделей. Список его функций включает:

  • встроенное литье типов
  • Проверка
  • построение запросов
  • перехватчики через промежуточное ПО

Mongoose поддерживает только MongoDB, поэтому для установки требуется только один пакет:

npm install mongoose

Ниже приведен пример кода установки:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test', {useNewUrlParser: true, useUnifiedTopology: true});

// With Mongoose, everything is derived from a Schema.
const kittySchema = new mongoose.Schema({
   name: {
    type: String,
    required: true
  }
});
const Kitten = mongoose.model('Kitten', kittySchema);

const fluffy = new Kitten({ name: 'fluffy' });
fluffy.save(function (err, fluffy) {
    if (err) return console.error(err);
    console.log(fluffy.name, 'saved!')
  });

В Mongoose есть два способа определения запросов. Ниже приведены оба примера:

// With a JSON doc
Person.
  find({
    occupation: /host/,
    'name.last': 'Ghost',
    age: { $gt: 17, $lt: 66 },
    likes: { $in: ['vaporizing', 'talking'] }
  }).
  limit(10).
  sort({ occupation: -1 }).
  select({ name: 1, occupation: 1 }).
  exec(callback);

// Using query builder
Person.
  find({ occupation: /host/ }).
  where('name.last').equals('Ghost').
  where('age').gt(17).lt(66).
  where('likes').in(['vaporizing', 'talking']).
  limit(10).
  sort('-occupation').
  select('name occupation').
  exec(callback);

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

Одним из ключевых преимуществ Mongoose перед всеми другими перечисленными здесь библиотеками ORM с открытым исходным кодом является то, что ее разработка спонсируется платформой Tidelift. Это означает, что проблемы безопасности выявляются и исправляются на раннем этапе.

Одним из недостатков является то, что Mongoose официально не поддерживает TypeScript. Неофициально вы можете использовать TypeScript, но для поддержки ваших моделей потребуется немного дополнительной работы. К счастью, следующая библиотека ORM, которую мы рассмотрим, решит эту проблему.

Библиотеки ORM TypeScript

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

Typegoose

  • Launch: March 2017
  • GitHub: Used by 2k
  • Databases: MongoDB

Typegoose — это «оболочка» для простого написания моделей Mongoose с помощью TypeScript. Эта библиотека решает проблему необходимости поддерживать отдельную модель Mongoose и интерфейс TypeScript. С Typegoose вам нужно только определить схему вашей модели с помощью интерфейса Typegoose.

Под капотом он использует API Reflect и Reflect-metadata для извлечения типов свойств, поэтому избыточность может быть значительно уменьшена.

Для установки Typegoose в ваши проекты требуется несколько пакетов:

npm i -s @typegoose/typegoose # install typegoose itself
npm i -s mongoose # install peer-dependency mongoose
npm i -D @types/mongoose # install all types for mongoose

Ниже приведен пример модели Mongoose, написанной на JavaScript:

const kittenSchema = new mongoose.Schema({
  name: String
});

const Kitten = mongoose.model('Kitten', kittenSchema);

let document = await Kitten.create({ name: 'Kitty' });
// "document" has no types

Ниже представлена ​​та же модель, написанная на TypeScript с использованием библиотеки Typegoose:

class KittenClass {
  @prop()
  public name?: string;
}

const Kitten = getModelForClass(KittenClass);

let document = await Kitten.create({ name: 'Kitty' });
// "document" has proper types of KittenClass

В следующем примере кода показан процесс настройки и выполнение команд CRUD:

import { prop, getModelForClass } from '@typegoose/typegoose';
import * as mongoose from 'mongoose';

class User {
  @prop()
  public name?: string;

  @prop({ type: () => [String] })
  public jobs?: string[];
}

const UserModel = getModelForClass(User); // UserModel is a regular Mongoose Model with correct types

(async () => {
  await mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true, dbName: "test" });

  const { _id: id } = await UserModel.create({ name: 'JohnDoe', jobs: ['Cleaner'] } as User); // an "as" assertion, to have types for all properties
  const user = await UserModel.findById(id).exec();

  console.log(user); // prints { _id: 59218f686409d670a97e53e0, name: 'JohnDoe', __v: 0 }
})();

Поскольку Typegoose — это просто оболочка TypeScript для библиотеки ORM, вам нужно будет посмотреть документацию Mongoose, чтобы узнать, как выполнять задачи CRUD.

TypeORM

  • Launch : Feb 21, 2016
  • GitHub: Used by 71.8k
  • Slack
  • Databases : MySQL, MariaDB, Postgres, CockroachDB, SQLite, Microsoft SQL Server, Oracle, SAP Hana, sql.js and MongoDB

TypeORM в настоящее время является самой популярной библиотекой ORM, созданной для проектов TypeScript. Он может работать на многих платформах, в том числе:

  • Node.js
  • браузер
  • на мобильных устройствах — Cordova, PhoneGap, Ionic, React Native и NativeScript
  • Electron

Библиотека также поддерживает шаблоны Active Record и Data Mapper, что позволяет разработчикам создавать высококачественные, масштабируемые и удобные в обслуживании приложения, управляемые базами данных. На него сильно влияют другие ORM, такие как Hibernate, Doctrine и Entity Framework. Это означает, что разработчики с опытом работы на Java и Ruby будут чувствовать себя как дома.

TypeORM — это ORM, который может работать на платформах Node.js, браузере, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo и Electron, а также может использоваться с TypeScript и JavaScript. Его цель — всегда поддерживать новейшие функции JavaScript и предоставлять дополнительные функции, которые помогут вам разрабатывать любые приложения, использующие базы данных — от небольших приложений с несколькими таблицами до крупномасштабных корпоративных приложений с несколькими базами данных.

Для установки TypeORM требуется установить несколько пакетов, включая адаптеры баз данных и дополнительные пакеты TypeScript:

npm install typeorm --save

# You need to install reflect-metadata shim:
npm install reflect-metadata --save

# and import it somewhere in the global place of your app (for example in app.ts):
# import "reflect-metadata";

# You may need to install node typings:
npm install @types/node --save-dev

# Install a database driver:
npm install mysql --save (you can install mysql2 instead as well)
npm install pg --save
npm install sqlite3 --save
npm install mssql --save
npm install sql.js --save
# To make the Oracle driver work, you need to follow the installation instructions from their site.
npm install oracledb --save
# for SAP Hana
npm i @sap/hana-client
npm i hdb-pool
# for MongoDB (experimental)
npm install mongodb --save

Далее вам нужно будет включить следующие настройки в tsconfig.json:

"emitDecoratorMetadata": true,
"experimentalDecorators": true,

Вам также может потребоваться включить es6в libразделе параметров компилятора или установить es6-shimиз @types.

В качестве альтернативы, вместо того, чтобы вручную настраивать проект TypeORM, вы можете просто использовать инструмент TypeORM CLI, чтобы сформировать проект за вас:

npm install typeorm -g
typeorm init --name MyProject --database mysql

Модели можно определять с помощью реализации DataMapper:

// Define entity model first
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    age: number;

}

// Perform CRUD tasks
const repository = connection.getRepository(User);

const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await repository.save(user);

const allUsers = await repository.find();
const firstUser = await repository.findOne(1); // find by id
const timber = await repository.findOne({ firstName: "Timber", lastName: "Saw" });

await repository.remove(timber);

В качестве альтернативы вы можете использовать шаблон Active Record для определения ваших моделей:

import {Entity, PrimaryGeneratedColumn, Column, BaseEntity} from "typeorm";

@Entity()
export class User extends BaseEntity {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    age: number;

}

const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await user.save();

const allUsers = await User.find();
const firstUser = await User.findOne(1);
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });

await timber.remove();

TypeORM предоставляет несколько способов создания запросов с помощью собственного Query Builder. Вот один из его примеров:

const firstUser = await connection
    .getRepository(User)
    .createQueryBuilder("user")
    .where("user.id = :id", { id: 1 })
    .getOne();

Ниже представлен вывод SQL:

SELECT
    user.id as userId,
    user.firstName as userFirstName,
    user.lastName as userLastName
FROM users user
WHERE user.id = 1

Вот пример сложного запроса:

const posts = await connection.getRepository(Post)
    .createQueryBuilder("post")
    .where(qb => {
        const subQuery = qb.subQuery()
            .select("user.name")
            .from(User, "user")
            .where("user.registered = :registered")
            .getQuery();
        return "post.title IN " + subQuery;
    })
    .setParameter("registered", true)
    .getMany();

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

Из-за огромного количества функций, которые поддерживает библиотека, накопившийся объем нерешенных проблем достиг значительного уровня, что ложится тяжелым бременем на разработчиков ядра. Этот вопрос был рассмотрен разработчиками здесь, где они обсуждают будущее TypeORM.

Тем не менее, TypeORM в настоящее время является самой популярной ORM для TypeScript. Это означает, что найти разработчиков, знакомых с библиотекой, будет проще, когда дело доходит до поддержки вашего проекта в долгосрочной перспективе. Надеюсь, что больше участников присоединятся к основной команде обслуживания и помогут стабилизировать ORM.

MikroORM

  • Launch: Mar 11, 2018
  • GitHub: Used by 206
  • Slack
  • Databases : MongoDB, MySQL, MariaDB, PostgreSQL and SQLite

MikroORM — один из самых молодых участников Node.js TypeScript ORM в этом списке. Он поддерживает как базы данных SQL, так и NoSQL, что является удивительным достижением, которого удалось достичь не многим ORM. Он во многом вдохновлен Doctrine и Nextras ORM. Любой, кто знаком с ними, должен чувствовать себя с MikroORM как дома.

Библиотека оптимизирована для транзакций и производительности с помощью шаблонов Identity Map. Он также поддерживает шаблон Data Mapper. Документация отлично, с легкой навигацией по конкретным темам. Одним из ключевых преимуществ использования новых библиотек ORM является то, что они предназначены для преодоления многих архитектурных проблем, с которыми сталкиваются старые и большие библиотеки.

Просматривая образцы кода, которые я предоставил, вы заметите, что синтаксис намного проще для понимания. Это ключ к созданию крупномасштабных проектов, которые будут поддерживать в долгосрочной перспективе. Теперь пройдемся по процессу установки:

npm i -s @mikro-orm/core @mikro-orm/mongodb     # for mongo
npm i -s @mikro-orm/core @mikro-orm/mysql       # for mysql
npm i -s @mikro-orm/core @mikro-orm/mariadb     # for mariadb
npm i -s @mikro-orm/core @mikro-orm/postgresql  # for postgresql
npm i -s @mikro-orm/core @mikro-orm/sqlite      # for sqlite

Далее вам нужно включить поддержку декораторов и esModuleInteropв tsconfig.json:

"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,

Затем вызовите MikroORM.initкак часть начальной загрузки вашего приложения:

const orm = await MikroORM.init({
  entities: [Author, Book, BookTag],
  dbName: 'my-db-name',
  type: 'mongo', // one of `mongo` | `mysql` | `mariadb` | `postgresql` | `sqlite`
  clientUrl: '...', // defaults to 'mongodb://localhost:27017' for mongodb driver
});
console.log(orm.em); // access EntityManager via `em` property

MikroORM предоставляет инструмент командной строки @mikro-orm/cli, доступ к которому вы можете получить, используя npxили установив локально и получив доступ к нему следующим образом:

# manually
$ node node_modules/.bin/mikro-orm
# via npx
$ npx mikro-orm
# or via yarn
$ yarn mikro-orm

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

  • управление схемой
  • импорт файла SQL в базу данных
  • генерирующие объекты
  • миграция базы данных

MikroORM предоставляет три способа определения классов сущностей. Вот один пример использования синтаксиса метаданных отражения:

@Entity()
export class Book extends BaseEntity {

  @Property()
  title!: string;

  @ManyToOne(() => Author)
  author!: Author;

  @ManyToOne(() => Publisher, { wrappedReference: true, nullable: true })
  publisher?: IdentifiedReference<Publisher>;

  @ManyToMany({ entity: 'BookTag', fixedOrder: true })
  tags = new Collection<BookTag>(this);

}

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

// use constructors in your entities for required parameters
const author = new Author('Jon Snow', 'snow@wall.st');
author.born = new Date();

const publisher = new Publisher('7K publisher');

const book1 = new Book('My Life on The Wall, part 1', author);
book1.publisher = publisher;
const book2 = new Book('My Life on The Wall, part 2', author);
book2.publisher = publisher;
const book3 = new Book('My Life on The Wall, part 3', author);
book3.publisher = publisher;

// just persist books, author and publisher will be automatically cascade persisted
await orm.em.persistAndFlush([book1, book2, book3]);

// or one by one
orm.em.persist(book1);
orm.em.persist(book2);
orm.em.persist(book3);
await orm.em.flush(); // flush everything to database at once

// Update existing book
const book = await orm.em.findOne(Book, 1);
book.title = 'How to persist things...';

// no need to persist `book` as its already managed by the EM
await orm.em.flush();

// Retrieve all books
const books = await orm.em.find(Book, {});
for (const book of books) {
  console.log(book.title);
}

Запросы сущностей могут выполняться с помощью объекта условий, известного как FilterQuery. Вот разные примеры:

// search by entity properties
const users = await orm.em.find(User, { firstName: 'John' });

// for searching by reference you can use primary key directly
const id = 1;
const users = await orm.em.find(User, { organization: id });

// or pass unpopulated reference (including `Reference` wrapper)
const ref = await orm.em.getReference(Organization, id);
const users = await orm.em.find(User, { organization: ref });

// fully populated entities as also supported
const ent = await orm.em.findOne(Organization, id);
const users = await orm.em.find(User, { organization: ent });

// complex queries with operators
const users = await orm.em.find(User, { $and: [{ id: { $nin: [3, 4] } }, { id: { $gt: 2 } }] });

// you can also search for array of primary keys directly
const users = await orm.em.find(User, [1, 2, 3, 4, 5]);

// and in findOne all of this works, plus you can search by single primary key
const user1 = await orm.em.findOne(User, 1);

Библиотека также поддерживает:

  • получение частичных сущностей
  • получение результатов с разбивкой на страницы
  • с использованием пользовательских фрагментов SQL

Чтобы выполнять еще более сложные запросы, вы можете использовать Конструктор запросов. Вот пример кода:

const qb = orm.em.createQueryBuilder(Author);
qb.update({ name: 'test 123', type: PublisherType.GLOBAL }).where({ id: 123, type: PublisherType.LOCAL });

console.log(qb.getQuery());
// update `publisher2` set `name` = ?, `type` = ? where `id` = ? and `type` = ?

console.log(qb.getParams());
// ['test 123', PublisherType.GLOBAL, 123, PublisherType.LOCAL]

// run the query
const res1 = await qb.execute();

Построитель запросов MikroORM использует Knex.js, к которому вы можете получить доступ через qb.getKnexQuery()функцию. Это означает, что могут быть выполнены все сложные и необработанные SQL-запросы, которые вы хотите создать и выполнить. Следовательно, вы получаете преимущества гибкости и производительности, выбирая MikroORM в своем техническом стеке. В документации по Query Builder есть много примеров построения запросов, включая различные типы объединений, которых слишком много, чтобы перечислять здесь. Вам будет приятно узнать, что построитель запросов предоставляет функцию для отображения выходных данных SQL во время разработки без включения параметра отладки. Вот пример:

const qb = orm.em.createQueryBuilder(BookTag, 't');
qb.select(['b.*', 't.*'])
  .leftJoin('t.books', 'b')
  .where('b.title = ? or b.title = ?', ['test 123', 'lol 321'])
  .andWhere('1 = 1')
  .orWhere('1 = 2')
  .limit(2, 1);

console.log(qb.getQuery());
// select `b`.*, `t`.*, `e1`.`book_tag_id`, `e1`.`book_uuid_pk` from `book_tag` as `t`
// left join `book_to_book_tag` as `e1` ON `t`.`id` = `e1`.`book_tag_id`
// left join `book` as `b` ON `e1`.`book_uuid_pk` = `b`.`uuid_pk`
// where (((b.title = ? or b.title = ?) and (1 = 1)) or (1 = 2))
// limit ? offset ?

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

Prisma

  • Launch: April 2019
  • GitHub: Used by 5.7k
  • Databases : PostgreSQL, MySQL, SQLite, SQL Server

Prisma — это самая последняя версия ORM TypeScript в этой статье. Он описывает себя как «ORM следующего поколения», который упрощает работу с базами данных для разработчиков приложений. Он предоставляет следующие инструменты:

  • Prisma Client: клиентская библиотека, обеспечивающая безопасный доступ к базе данных.
  • Prisma Migrate ( предварительная версия ): инструмент миграции, который автоматически создается при внесении изменений в файл схемы.
  • А также Prisma Studio: современный графический интерфейс для просмотра и управления данными в вашей базе данных

Prisma сильно отличается от всех других ORM, которые мы рассматривали. Он не использует объектные модели (классы сущностей), а скорее файл схемы для сопоставления всех таблиц и столбцов. Этот файл используется средством миграции для создания файла миграции SQL и клиентской библиотекой для создания определений типов. Все сгенерированные определения типов хранятся в.prisma/client/index.d.tsпапке. Вот пример сгенерированного представления для Userтипа:

export declare type User = {
  id: string
  email: string
  name: string | null
}

Вы могли заметить, что postsссылка в модели отсутствует в определении TypeScript. Рекомендуемое решение — создать разновидность такого Userтипа:

import { Prisma } from '@prisma/client'
// Define a type that includes the relation to `Post`
type UserWithPosts = Prisma.UserGetPayload<{
  include: { posts: true }
}>

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

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

Настройка проекта Prisma — это небольшой процесс, полные инструкции по которому вы можете найти здесь. Пока мы просто оцениваем. Вот основные этапы установки:

npm install prisma typescript ts-node @types/node --save-dev

Вам нужно будет выполнить обновление tsconfig.jsonследующим образом:

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true
  }
}

Начните с создания модели данных вашего приложения в файле схемы, расположенном по адресу prisma/schema.prisma:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_UR
}

model Post {
  id        Int      @default(autoincrement()) @id
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String   @db.VarChar(255)
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

model Profile {
  id     Int     @default(autoincrement()) @id
  bio    String?
  user   User    @relation(fields: [userId], references: [id])
  userId Int     @unique
}

model User {
  id      Int      @default(autoincrement()) @id
  email   String   @unique
  name    String?
  posts   Post[]
  profile Profile?
}

Затем вам нужно сопоставить вашу модель данных со схемой базы данных с помощью prisma migrateинструмента CLI:

npx prisma migrate dev --name init --preview-feature

Мы пропустим процесс установки и посмотрим на наш установочный код index.ts:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  const allUsers = await prisma.user.findMany()
  console.log(allUsers)
}

main()
  .catch(e => {
    throw e
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

Ниже приведен пример, демонстрирующий, как сохранять данные и записи запросов:

async function main() {
  await prisma.user.create({
    data: {
      name: 'Alice',
      email: 'alice@prisma.io',
      posts: {
        create: { title: 'Hello World' },
      },
      profile: {
        create: { bio: 'I like turtles' },
      },
    },
  })

  const allUsers = await prisma.user.findMany({
    include: {
      posts: true,
      profile: true,
    },
  })
  console.dir(allUsers, { depth: null })
}

Когда вы запустите приведенный выше код, результаты будут возвращены в виде объектов JavaScript, например:

[
  {
    email: 'alice@prisma.io',
    id: 1,
    name: 'Alice',
    posts: [
      {
        content: null,
        createdAt: 2020-03-21T16:45:01.246Z,
        id: 1,
        published: false,
        title: 'Hello World',
        authorId: 1,
      }
    ],
    profile: {
      bio: 'I like turtles',
      id: 1,
      userId: 1,
    }
  }
]

Документация Prisma выглядит красиво и, похоже, содержит много контента. К сожалению, мне было сложно найти нужную информацию. Либо это из-за слишком сложной системы навигации, либо из-за отсутствия конкретного контента. Информация распределена по нескольким разделам, включая:

  • концепции
  • гиды
  • Справка
  • статьи поддержки / справки

Prisma — это более новая библиотека, которая следует другой философии построения уровня данных. Кроме того, похоже, что он растет быстрее, чем MikroORM, тем более, что он был запущен годом позже.

Заключение

В заключение я хотел бы кратко обсудить доводы против использования библиотек ORM в вашем проекте. К основным аргументам можно отнести:

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

Вы можете прочитать все аргументы против использования библиотек ORM здесь и здесь.

Изучив все текущие библиотеки ORM JavaScript и TypeScript, вы должны знать, что каждая из них отличается своей реализацией. Большинство аргументов против библиотек ORM было разрешено новыми, такими как Object.js и Prisma. Если вы решите не использовать библиотеку ORM, вам придется выбрать отдельные инструменты и библиотеки, которые составляют ваш стек уровня данных.

На мой взгляд, выбор ORM для вашего проекта — лучшее решение по одной причине: документации.

Как разработчики, мы очень плохо документируем собственный код. Если бы мы реализовали индивидуальное решение или внедрили библиотеку, которая не очень хорошо известна, будущим сопровождающим было бы сложно поддерживать ваше приложение в соответствии с потребностями бизнеса.

Однако, если вы используете хорошо документированную библиотеку ORM, им станет намного проще работать с вашим приложением еще долго после того, как вы покинули проект. Это связано с тем, что ORM прививают хорошие практики кода, такие как архитектура и шаблоны, такие как Data Mapper. И хотя это может потребовать обучения, в долгосрочной перспективе это лучше.

Надеюсь, я предоставил полезную информацию, которая поможет вам оценить библиотеку ORM для вашего проекта. Если вам нужна рекомендация, выберите библиотеку ORM TypeScript, которая больше всего подходит для проекта корпоративного класса.

 

Оцените статью
bestprogrammer.ru
Добавить комментарий