# Использование sequelize для получения данных из БД Продолжаем разработку АПИ для проекта "ресторан" используя **express.js**. **Содержание:** * [Создание проекта](./express01.md#создание-проекта) * [Подключение к БД](./express02.md#подключение-к-бд) * [Использование sequelize для получения данных из БД](#использование-sequelize-для-получения-данных-из-бд) * [Создание скрипта для наполнения БД начальными данными](#создание-скрипта-для-наполнения-бд-начальными-данными) * [Добавление сущностей БД, реализация REST.](#добавление-сущностей-бд-реализация-rest) * [JWT-авторизация](#jwt-авторизация) Возвращаемся к нашему `app.js` Добавляем в начале файла импорт библиотеки ```js const { sequelize } = require('./models') const { QueryTypes } = require('sequelize') ``` И добавим _endpoint_ для получения списка блюд (я добавил префикс `/api`, он нам понадобится при настройке **nginx**, когда будем заворачивать апи в контейнер): ```js app.get('/api/menu-item', async (req, res) => { try { res.json(await sequelize.query(` SELECT * FROM MenuItem `, { logging: false, type: QueryTypes.SELECT })) } catch (error) { console.error(error) } finally { res.end() } }) ``` 1. Запросы к БД асинхронные, поэтому заворачиваем код в async/await 1. Список блюд, возвращаемый запросом `sequelize.query` заворачиваем в `res.json()`, это добавит в заголовок ответа `Content-Type: application/json`. Пока у нас таблица блюд пустая, поэтому в ответе тоже будет пустой массив ## Создание скрипта для наполнения БД начальными данными **Sequelize** позволяет добавить записи в таблицы (можно использовать для первоначальной инициализации словарей или при тестировании) ``` npx sequelize-cli seed:generate --name menu-items ``` В каталоге `seeders` будет создан файл аналогичный файлам миграции (собственно и отличий между ними нет, просто логически выделены отдельно) С помощью метода `bulkInsert` формируем список блюд для добавления: ```js 'use strict'; /** @type {import('sequelize-cli').Migration} */ module.exports = { async up (queryInterface, Sequelize) { await queryInterface.bulkInsert('MenuItem', [ {id: 1, title: 'Салат', image: 'Салат.jpg', price: 100}, {id: 2, title: 'Суп', image: 'Суп.jpg', price: 100}, {id: 3, title: 'Компот', image: 'Компот.jpg', price: 100} ]) }, async down (queryInterface, Sequelize) { await queryInterface.bulkDelete( 'MenuItem', null /* тут можно прописать условие WHERE */ ) } } ``` И запускаем добавление данных командой ``` npx sequelize-cli db:seed:all [--url=...] ``` Можно откатить отдельный импорт, но накатить можно только всё разом. Поэтому для заполнения словарей лучше использовать _bulkInsert_ в обычной миграции, а seeders использовать для тестов. ## Добавление сущностей БД, реализация REST. Добавим в БД таблицу _Cart_ (корзина) и связи между _Cart_ и _MenuItem_ ``` npx sequelize-cli migration:generate --name cart ``` И пропишем в созданном файле команды для создания таблицы с внешним ключем и команды для удаления: >В реальном проекте ещё нужно добавить таблицу пользователей и привязать корзину к пользователю ```js 'use strict'; /** @type {import('sequelize-cli').Migration} */ module.exports = { async up (queryInterface, Sequelize) { await queryInterface.createTable('Cart', { id: { allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.DataTypes.INTEGER }, menuItemId: { type: Sequelize.DataTypes.INTEGER, allowNull: false, comment: 'для внешнего ключа' }, quantity: { type: Sequelize.DataTypes.INTEGER, allowNull: false, comment: 'количество' } }) /** * Внешний ключ корзина_блюдо */ await queryInterface.addConstraint('Cart', { fields: ['menuItemId'], type: 'foreign key', name: 'FK_cart_menu-item', references: { table: 'MenuItem', field: 'id' }, onDelete: 'no action', onUpdate: 'no action' }) }, async down (queryInterface, Sequelize) { await queryInterface.removeConstraint('Cart', 'FK_cart_menu-item') await queryInterface.dropTable('Cart') } } ``` Накатываем миграцию командой `npx sequelize-cli db:migrate` и у нас в схеме бд появится таблица Cart: ![](./img/cart.png) ### Роутер Мы уже знаем как создавать конечные точки, и можем даже добавить в них параметры пути, например, написать такую конечную точку, чтобы получить детальную информацию по блюду: ```js app.get('/api/menu-item/:id', (req, res) => { // req.params.id }) ``` Но базовый синтаксис __express.js__ не позволяет задавать тип параметров передаваемых в пути запроса, мы не можем явно указать, что `:id` должен быть целым числом. Для этих целей в __express.js__ есть класс __Router__. Перепишем наше приложение, чтобы использовать роутеры: 1. Создайте каталог `routes` в котором будем создавать отдельные файлы для сущностей 1. Создайте файл `routes/menu_item.js`, добавьте в него обвязку для роутера и перенесите в него конечные точки для модели `menu-item`: ```js const express = require('express') // создаем экземпляр роутера и экспортируем его из модуля const router = express.Router() module.exports = router // описываем конечные точки роутера // обратите внимание - начало пути (/api/menu-item) мы тут не пишем // тип параметра задаем используя regex-формат router.get('/:id(\\d+)', async (req, res) => { ... }) ``` 1. В файле `app.js` подключаем созданные роутеры ```js // импортируем маршруты const menuItemRouter = require('./routes/menu_item') const app = express() // цепляем все запросы начинающиеся с "/api/menu-item" с маршруту app.use('/api/menu-item', menuItemRouter) ... ``` Таким образом мы не только обеспечим описание типа переменных пути запроса, но и вынем однотипные конечные точки в разные файлы. ### CRUD для корзины >Для того, чтобы тело запроса автоматически преобразовывалось в объект _body_ нужно после создания экземпляра приложения подключить _middleware_ `express.json()` (_middleware_ это функции, которые выполняются __до обработки__ _endpoints_, используются для служебных целей, как в нашем случае, и в целях авторизации. Позже добавим jwt-авторизацию и напишем для неё _middleware_): ```js ... const app = express() app.use(express.json()) ... ``` #### Create. Для добавления записей в таблицу используется метод POST >Используется старый синтаксис без роутера, но вы пишите сразу правильно ```js app.post('/api/cart', async function(req, res) { try { await sequelize.query(` INSERT INTO Cart (menuItemId, quantity) -- значения :menuItemId и :quantity задаются в replacements VALUES (:menuItemId, :quantity) `,{ logging: false, type: QueryTypes.INSERT, // объект replacements должен содержать значения для замены replacements: { menuItemId: req.body.menuItemId, quantity: req.body.quantity } }) // ничего не возвращаем, но код ответа меняем на 201 (Created) // можно и не менять, по-умолчанию возвращает 200 (OK) res.status(201) } catch (error) { console.warn('ошибка при добавлении блюда в корзину:', error.message) // при ошибке возвращаем код ошибки 500 и текст ошибки res.status(500).send(error.message) } finally { res.end() } }) ``` #### Read. Чтение Используется метод get. Пример приводить не буду, он аналогичен запросу списка блюд #### Update. Редактирование корзины Для редактирования в REST используются методы PUT или PATCH. PUT - полная перезапись (замещение объекта), PATCH - частичная перезапись (изменение части объекта) Мы будем только менять количество, поэтому используем PATCH ```js // в пути можно описать параметр (поставив знак ":") app.patch('/api/cart/:id', async (req, res) => { try { await sequelize.query(` UPDATE Cart SET quantity=:quantity WHERE id=:id `,{ logging: false, replacements: { // используем параметр из пути id: req.params.id, quantity: req.body.quantity } }) } catch (error) { console.warn('ошибка при редактировании корзины:', error.message) res.status(500).send(error.message) } finally { res.end() } }) ``` #### Delete. Удаление блюда из корзины Тут ничего нового ```js app.delete('/api/cart/:id', async (req, res) => { try { await sequelize.query(` DELETE FROM Cart WHERE id=:id `,{ logging: false, replacements: { id: req.params.id } }) } catch (error) { console.warn('ошибка при удалении блюда из корзины:', error.message) res.status(500).send(error.message) } finally { res.end() } }) ``` ## Задание Реализовать АПИ (CRUD всех моделей) для своего курсового проекта по образцу.