Продолжаем разработку АПИ для проекта "ресторан" используя express.js.
Содержание:
Для подключения к БД можно использовать пакет mysql, но для более-менее сложных проектов, подразумевающих дальнейшее развитие, лучше использовать библиотеки, поддерживающие миграции (инициализация и изменение структуры базы данных). Для JavaScript наиболее популярна ORM библиотека sequelize. На хабре есть цикл статей, посвящённый этой библиотеке.
Всеми возможностями мы пользоваться не будем, нам достаточно миграций.
Выполняем в каталоге с проектом
npm i mysql2 sequelize sequelize-cli
mysql2 нужен для работы с БД MySQL (явно мы его не используем, но он нужен для sequelize)sequelize нужен для использования в нашем кодеsequelize-cli нужна для создания и управления миграциямиnpx sequelize-cli init
Будут созданы следующие каталоги:
config — в этом каталоге лежит файл с настройками подключения к БДmodels — модели для проектаmigrations — файлы с миграциямиseeders — файлы для заполнения БД начальными даннымиДалее нам нужно сообщить консольной утилите (sequelize-cli), как подключаться к БД. Для этого откроем файл config/config.json. Он выглядит примерно так:
{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
То есть для разных сценариев мы можем использовать разные БД:
Хранить логин/пароль к БД в открытом доступе нельзя, поэтому перепишите config/config.json таким образом:
{
"development": {
"use_env_variable": "DATABASE_URL",
"dialect": "mysql"
},
"test": {
"use_env_variable": "DATABASE_URL",
"dialect": "mysql"
},
"production": {
"use_env_variable": "DATABASE_URL",
"dialect": "mysql"
}
}
В каталоге
modelsесть файлindex.jsв котором происходит инициализация подключения, нам в нём ничего менять не нужно, но я приведу кусок кода из него, чтобы было понятно где используется параметрuse_env_variable>if (config.use_env_variable) { > sequelize = new Sequelize(process.env[config.use_env_variable], config); >} else { > sequelize = new Sequelize(config.database, config.username, config.password, config); >} >``` > >То есть, если в конфиге есть параметр `use_env_variable`, то в конструктор передается строка подключения из переменной окружения, которая задана в этом параметре (в нашем случае `DATABASE_URL`). Иначе используется конструктор с параметрами `database`, `username` и `password`. **Sequelize** поддерживает загрузку строки подключения из переменных окружения. Нам нужно создать переменную окружения в таком формате:DATABASE_URL=mysql://user:password@sqldomain/db_name
В **VSCode** можно задать переменные в настройках:   И в разделе `"configurations"` добавьте объект `"env"`:  ## Создание базы данных >Базу можно создать и в менеджере баз данных, но сделаем в консоли, чтобы проверить все настройки Для создания БД используется команда:npx sequelize-cli db:create [--url=<строка подключения>]
Параметр `--url <строка подключения к БД>` можно не указывать, если: * оставили стандартный вариант инициализации БД или в переменных окружения есть `DATABASE_URL` (учитывайте, что на командную строку не распространяются настройки VSCode) * в корне проекта создан файл `.sequelizerc`, в котором задана переменная `url`: ```js module.exports = { 'url': 'mysql://ekolesnikov:<тут пароль>@kolei.ru/restaurant' } ``` Такой вариант предпочтительнее, чтобы не писать `url` при каждой миграции (мы рассматриваем вариант, когда в конфиге не хранятся логин и пароли, а используется переменная окружения). Только не забудьте и этот файл прописать в `.gitignore` Если всё настроили правильно, то при запуске команды `npx sequelize-cli db:create` выдаст примерно такое (и на сервере MySQL будет создана БД, которая указана в строке подключения):Sequelize CLI [Node: 23.9.0, CLI: 6.6.3, ORM: 6.37.7]
Parsed url mysql://ekolesnikov:*****@kolei.ru/restaurant Database restaurant created.
## Создадим миграцию для начальной инициализации базы данных
Миграции создаются командой:
npx sequelize-cli migration:generate --name first
>Вместо `first` лучше писать что-то человеко-понятное, например `create_MenuItem`
В каталоге `migrations` будет создан файл `YYYYMMDDhhmmss-first.js`:
js 'use strict';
/** @type {import('sequelize-cli').Migration} */ module.exports = { async up (queryInterface, Sequelize) {
/**
* Add altering commands here.
*
* Example:
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
},
async down (queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
} };
В методе `up` мы должны прописать команды для создания таблиц и связей, а в методе `down` для удаления. В первой миграции мы создадим таблицу `MenuItem` для хранения блюд (тут вроде всё более менее понятно, подробно расписывать не буду - если что-то не понятно, то можно загуглить):
js 'use strict';
/** @type {import('sequelize-cli').Migration} */ module.exports = { async up (queryInterface, Sequelize) {
await queryInterface.createTable('MenuItem', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.DataTypes.INTEGER
},
title: {
type: Sequelize.DataTypes.STRING,
allowNull: false,
comment: 'название блюда'
},
image: {
type: Sequelize.DataTypes.STRING,
allowNull: false,
comment: 'название файла с изображением блюда'
},
description: {
type: Sequelize.DataTypes.TEXT,
allowNull: true,
comment: 'описание блюда'
},
price: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false
}
})
},
async down (queryInterface, Sequelize) {
await queryInterface.dropTable('MenuItem')
} }
И "накатим" её командой:
npx sequelize-cli db:migrate [--url <строка подключения к БД>]
Логи консоли:
npx sequelize-cli db:migrate
Sequelize CLI [Node: 23.9.0, CLI: 6.6.3, ORM: 6.37.7]
Parsed url mysql://ekolesnikov:*****@kolei.ru/restaurant == 20250514112031-first: migrating ======= == 20250514112031-first: migrated (0.240s)
Если вдруг что-то забыли, то можно "откатить" последнюю миграцию командой:
npx sequelize-cli db:migrate:undo
Дополнительные примеры создания миграций:
js async up (queryInterface, Sequelize) { // создаём словарь await queryInterface.createTable('NewsType', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.DataTypes.INTEGER
},
title: {
type: Sequelize.DataTypes.STRING(16),
allowNull: false
},
})
// заполняем словарь данными await queryInterface.bulkInsert('NewsType',[
{id: 1, title: 'Срочное'},
{id: 2, title: 'Важное'}
])
// создаём таблицу, в которой будет использоваться словарь await queryInterface.createTable('News', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.DataTypes.INTEGER
},
// поле для внешнего ключа
newsTypeId: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false
}
// тут еще много всяких полей
})
// создаём внешний ключ await queryInterface.addConstraint('News', {
fields: ['newsTypeId'],
type: 'foreign key',
// названия ключей должны быть уникальными
name: 'FK_News_NewsType',
references: {
table: 'NewsType',
field: 'id'
}
})
// Пример создания таблицы связей (многие-ко-многим) // особенность в том, что первичный ключ в таких таблицах составной // и создаётся отдельно await queryInterface.createTable('NewsRole', {
newsId: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false
},
roleId: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false
}
})
// добавление первичного ключа await queryInterface.addConstraint('NewsRole', {
fields: ['newsId', 'roleId'],
type: 'primary key',
name: 'PK_NewsRole'
})
// далее создаются внешние ключи для полей newsId и roleId
} ```
sequelizeсоздайте миграцию с командами создания таблиц и связей между ними (заполните словари, если они есть)
Этот пункт делать только после утверждения ERD