Sequelize - популярный, простой в использовании инструмент объектно-реляционного сопоставления (ORM) JavaScript, который работает с базами данных SQL. Довольно просто начать новый проект с помощью интерфейса командной строки Sequelize, но чтобы по-настоящему воспользоваться возможностями Sequelize, вам нужно определить отношения между своими моделями.
В этом пошаговом руководстве мы создадим проект Sequelize, чтобы назначать задачи конкретным пользователям. Мы воспользуемся ассоциациями для определения этой связи, а затем рассмотрим способы запроса базы данных на основе этих ассоциаций.
Начнем с установки Postgres, Sequelize и Sequelize CLI в новую папку проекта:
mkdir sequelize-associations cd sequelize-associations npm init -y npm install sequelize pg npm install --save-dev sequelize-cli
Затем давайте инициализируем проект Sequelize, а затем откроем весь каталог в нашем редакторе кода:
npx sequelize-cli init code .
Чтобы узнать больше о любой из команд Sequelize CLI ниже, см .:
Начало работы с Sequelize CLI
Давайте настроим наш проект Sequelize для работы с Postgres. Найдите config.json
в каталоге /config
и замените то, что там, на этот код:
{ "development": { "database": "sequelize_associations_development", "host": "127.0.0.1", "dialect": "postgres" }, "test": { "database": "sequelize_associations_test", "host": "127.0.0.1", "dialect": "postgres" }, "production": { "database": "sequelize_associations_production", "host": "127.0.0.1", "dialect": "postgres" } }
Круто, теперь мы можем сказать Sequelize создать базу данных:
npx sequelize-cli db:create
Далее мы создадим модель User
из командной строки:
npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string,password:string
При запуске model:generate
автоматически создается и файл модели, и миграция с указанными нами атрибутами. Вы можете найти эти файлы в каталоге своего проекта, но сейчас нет необходимости их менять. (Позже мы отредактируем файл модели, чтобы определить наши связи.)
Теперь мы выполним миграцию, чтобы создать Users
таблицу в нашей базе данных:
npx sequelize-cli db:migrate
Теперь давайте создадим исходный файл:
npx sequelize-cli seed:generate --name user
Вы увидите новый файл в /seeders
. В этот файл вставьте следующий код, чтобы создать демонстрационного пользователя «John Doe»:
module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.bulkInsert('Users', [{ firstName: 'John', lastName: 'Doe', email: '[email protected]', password: '$321!pass!123$', createdAt: new Date(), updatedAt: new Date() }], {}); }, down: (queryInterface, Sequelize) => { return queryInterface.bulkDelete('Users', null, {}); } };
После того, как мы сохранили наш исходный файл, давайте выполним его:
npx sequelize-cli db:seed:all
Перейдите в psql
и запросите базу данных, чтобы увидеть таблицу Users
:
psql sequelize_associations_development SELECT * FROM "Users";
Определение ассоциаций
Большой! У нас есть рабочая User
модель, но нашему Джону Доу, кажется, немного скучно. Давайте дадим Джону работу, создав Task
модель:
npx sequelize-cli model:generate --name Task --attributes title:string,userId:integer
Как и в случае с моделью User
выше, эта команда CLI Sequelize создаст как файл модели, так и миграцию на основе указанных нами атрибутов. Но на этот раз нам нужно будет отредактировать оба, чтобы связать наши модели вместе.
Сначала найдите task.js
в подкаталоге /models
в каталоге вашего проекта. Это модель Sequelize для задач, и вы увидите, что метод sequelize.define()
устанавливает title
и userId
как атрибуты, как мы указали выше.
Ниже вы увидите Task.associate
. В настоящее время он пуст, но именно здесь мы свяжем каждую задачу с userId
. Отредактируйте свой файл, чтобы он выглядел так:
module.exports = (sequelize, DataTypes) => { const Task = sequelize.define('Task', { title: DataTypes.STRING, userId: DataTypes.INTEGER }, {}); Task.associate = function(models) { // associations can be defined here Task.belongsTo(models.User, { foreignKey: 'userId', onDelete: 'CASCADE' }) }; return Task; };
Что делают эти изменения? Task.belongsTo()
устанавливает отношения« принадлежит с моделью User
, что означает, что каждая задача будет связана с конкретным пользователем.
Мы делаем это, устанавливая userId
как «внешний ключ», что означает, что он относится к ключу в другой модели. В нашей модели задачи должны принадлежать пользователю, поэтому userId
будет соответствоватьid
в конкретной записи User
. (onDelete: 'CASCADE'
настраивает нашу модель таким образом, что при удалении пользователя будут удалены и его задачи.)
Нам также необходимо изменить нашу User
модель, чтобы отразить другую сторону этих отношений. Найдите user.js
и измените раздел под User.associate
так, чтобы ваш файл выглядел так:
module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { firstName: DataTypes.STRING, lastName: DataTypes.STRING, password: DataTypes.STRING, email: DataTypes.STRING }, {}); User.associate = function(models) { // associations can be defined here User.hasMany(models.Task, { foreignKey: 'userId', }) }; return User; };
Для этой модели мы установили связь «имеет много», что означает, что у пользователя может быть несколько задач. В методе .hasMany()
параметр foreignKey
устанавливается равным имени ключа в таблице other. Другими словами, когда userId
в задаче совпадает с id
пользователя, у нас есть совпадение.
Нам все еще нужно внести еще одно изменение, чтобы настроить наши отношения в базе данных. В папке/migrations
вашего проекта вы должны увидеть файл, имя которого заканчивается на create-task.js
. Измените объект с меткой userId
так, чтобы ваш файл выглядел так, как показано ниже:
module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Tasks', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER }, title: { type: Sequelize.STRING }, userId: { type: Sequelize.INTEGER, onDelete: 'CASCADE', references: { model: 'Users', key: 'id', as: 'userId', } }, createdAt: { allowNull: false, type: Sequelize.DATE }, updatedAt: { allowNull: false, type: Sequelize.DATE } }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Tasks'); } };
Раздел references
настроит Tasks
таблицу в нашей базе данных, чтобы отразить те же отношения, которые мы описали выше. Теперь мы можем запустить нашу миграцию:
npx sequelize-cli db:migrate
Теперь наш John Doe готов взять на себя задачи, но у Джона по-прежнему нет никаких реальных задач. Давайте создадим исходный файл задачи:
npx sequelize-cli seed:generate --name task
Найдите только что сгенерированный исходный файл и вставьте следующее, чтобы создать задачу:
module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.bulkInsert('Tasks', [{ title: 'Build an app', userId: 1, createdAt: new Date(), updatedAt: new Date() }], {}); }, down: (queryInterface, Sequelize) => { return queryInterface.bulkDelete('Tasks', null, {}); } };
Мы установим userId
на 1
, чтобы задача принадлежала пользователю, которого мы создали ранее. Теперь мы можем заполнить базу данных.
npx sequelize-cli db:seed:all
Протестируйте базу данных:
psql sequelize_associations_development SELECT * FROM "Users" JOIN "Tasks" ON "Tasks"."userId" = "Users".id;
Запрос через Sequelize
Теперь мы можем запрашивать информацию в нашей базе данных на основе этих ассоциаций - и с помощью Sequelize мы можем делать это с помощью JavaScript, что упрощает интеграцию с приложением Node.js. Давайте создадим файл для хранения наших запросов:
touch query.js
Вставьте приведенный ниже код в новый файл:
const { User, Task } = require('./models') const Sequelize = require('sequelize'); const Op = Sequelize.Op // Find all users with their associated tasks // Raw SQL: SELECT * FROM "Users" JOIN "Tasks" ON "Tasks"."userId" = "Users".id; const findAllWithTasks = async () => { const users = await User.findAll({ include: [{ model: Task }] }); console.log("All users with their associated tasks:", JSON.stringify(users, null, 4)); } const run = async () => { await findAllWithTasks() await process.exit() } run()
Первые три строки выше импортируют наши модели User
и Task
вместе с Sequelize. После этого мы включаем функцию запроса, которая возвращает каждые User
вместе с задачами, связанными с этим пользователем.
Метод .findAll()
Sequelize принимает параметры как объект JavaScript. Выше мы использовали параметр include
, чтобы воспользоваться преимуществами« быстрой загрузки » - одновременного запроса данных из нескольких моделей. С этой опцией Sequelize вернет объект JavaScript, который включает каждый User
со всеми ассоциированными экземплярами Task
как вложенными объектами.
Давайте запустим наш файл запроса, чтобы увидеть это в действии:
node query.js
Теперь ясно, что у нашего Джона Доу есть над чем поработать! Мы можем использовать тот же метод для включения User
, когда наш запрос находит Task
. Вставьте следующий код в query.js
:
// Find a task with its associated user // Raw SQL: SELECT * FROM "Tasks" JOIN "Users" ON "Users"."id" = "Tasks"."userId"; const findTasksWithUser = async () => { const tasks = await Task.findAll({ include: [{ model: User }] }); console.log("All tasks with their associated user:", JSON.stringify(tasks, null, 4)); }
Измените const run
внизу query.js
, добавив строку для вызова findTasksWithUser()
. Теперь снова запустите свой файл в Node - каждый Task
должен включать информацию для User
, которому он принадлежит.
Запросы в этом пошаговом руководстве используют метод
.findAll()
. Чтобы узнать больше о других запросах Sequelize, см .: Использование интерфейса командной строки Sequelize и запросов
Вы также можете включить другие параметры рядом с include
, чтобы делать более конкретные запросы. Например, ниже мы воспользуемся параметром where
, чтобы найти только пользователей с именем John, но по-прежнему возвращать связанные задачи для каждого из них:
// Find all users named John with their associated tasks // Raw SQL: SELECT * FROM "Users" WHERE firstName = "John" JOIN tasks ON "Tasks"."userId" = "Users".id; const findAllJohnsWithTasks = async () => { const users = await User.findAll({ where: { firstName: "John" }, include: [{ model: Task }] }); console.log("All users named John with their associated tasks:", JSON.stringify(users, null, 4)); }
Вставьте указанное выше в свой query.js
и измените const run
на вызов findAllJohnsWithTasks()
, чтобы попробовать.
Теперь, когда вы знаете, как использовать ассоциации моделей в Sequelize, вы можете спроектировать свое приложение для доставки нужных вложенных данных. На следующем этапе вы можете решить включить более надежные исходные данные с помощью Faker или интегрировать приложение Sequelize с Express, чтобы создать сервер Node.js!
Эта статья написана в соавторстве с Джереми Роузом, инженером-программистом, редактором и писателем из Нью-Йорка.
Дополнительная информация о Sequelize CLI:
- Начало работы с Sequelize CLI
- Использование Sequelize CLI и запросов
- Продолжить CLI и Экспресс
- Начало работы с Sequelize CLI с помощью Faker
- Создайте Express API с помощью Sequelize CLI и Express Router
- Создание Express API с помощью Sequelize CLI и модульного тестирования!