# Подключение к БД Продолжаем разработку АПИ для проекта "ресторан" используя **express.js**. **Содержание:** * [Создание проекта](./express01.md#создание-проекта) * [Подключение к БД](#подключение-к-бд) * [Использование sequelize для получения данных из БД](#использование-sequelize-для-получения-данных-из-бд) * [Создание скрипта для наполнения БД начальными данными](#создание-скрипта-для-наполнения-бд-начальными-данными) * [Добавление сущностей БД, реализация REST.](#добавление-сущностей-бд-реализация-rest) * [JWT-авторизация](#jwt-авторизация) Для подключения к БД можно использовать пакет `mysql`, но для более-менее сложных проектов, подразумевающих дальнейшее развитие, лучше использовать библиотеки, поддерживающие миграции (инициализация и изменение структуры базы данных). Для **JavaScript** наиболее популярна __ORM__ библиотека [sequelize](https://sequelize.org/). На [хабре](https://habr.com/ru/articles/565062/) есть цикл статей, посвящённый этой библиотеке. Всеми возможностями мы пользоваться не будем, нам достаточно миграций. ## Установим зависимости >Выполняем в каталоге с проектом ``` npm i mysql2 sequelize sequelize-cli ``` * Пакет `mysql2` нужен для работы с БД MySQL (явно мы его не используем, но он нужен для **sequelize**) * Пакет `sequelize` нужен для использования в нашем коде * Консольная утилита `sequelize-cli` нужна для создания и управления миграциями ## Инициализация и настройка sequelize (только перед первым запуском) ### Инициализация: ``` npx sequelize-cli init ``` Будут созданы следующие каталоги: * `config` — в этом каталоге лежит файл с настройками подключения к БД * `models` — модели для проекта * `migrations` — файлы с миграциями * `seeders` — файлы для заполнения БД начальными данными ### Настройка Далее нам нужно сообщить консольной утилите (`sequelize-cli`), как подключаться к БД. Для этого откроем файл `config/config.json`. Он выглядит примерно так: ```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" } } ``` То есть для разных сценариев мы можем использовать разные БД: * **development** - режим разработки * **test** - тестирование * **production** - "боевая" БД Хранить логин/пароль к БД в открытом доступе нельзя, поэтому перепишите `config/config.json` таким образом: ```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` > >```js >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** можно задать переменные в настройках: ![](./launch_json.png) ![](./debugger.png) И в разделе `"configurations"` добавьте объект `"env"`: ![](./env.png) ## Создание базы данных >Базу можно создать и в менеджере баз данных, но сделаем в консоли, чтобы проверить все настройки Для создания БД используется команда: ``` 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