Продолжаем разработку АПИ для проекта "ресторан" используя express.js.
Содержание:
Возвращаемся к нашему app.js
Добавляем в начале файла импорт библиотеки
const { sequelize } = require('./models')
const { QueryTypes } = require('sequelize')
И добавим endpoint для получения списка блюд (я добавил префикс /api, он нам понадобится при настройке nginx, когда будем заворачивать апи в контейнер):
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()
}
})
sequelize.query заворачиваем в res.json(), это добавит в заголовок ответа Content-Type: application/json.Пока у нас таблица блюд пустая, поэтому в ответе тоже будет пустой массив
Sequelize позволяет добавить записи в таблицы (можно использовать для первоначальной инициализации словарей или при тестировании)
npx sequelize-cli seed:generate --name menu-items
В каталоге seeders будет создан файл аналогичный файлам миграции (собственно и отличий между ними нет, просто логически выделены отдельно)
С помощью метода bulkInsert формируем список блюд для добавления:
'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 использовать для тестов.
Добавим в БД таблицу Cart (корзина) и связи между Cart и MenuItem
npx sequelize-cli migration:generate --name cart
И пропишем в созданном файле команды для создания таблицы с внешним ключем и команды для удаления:
В реальном проекте ещё нужно добавить таблицу пользователей и привязать корзину к пользователю
'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:
Мы уже знаем как создавать конечные точки, и можем даже добавить в них параметры пути, например, написать такую конечную точку, чтобы получить детальную информацию по блюду:
app.get('/api/menu-item/:id', (req, res) => {
// req.params.id
})
Но базовый синтаксис express.js не позволяет задавать тип параметров передаваемых в пути запроса, мы не можем явно указать, что :id должен быть целым числом.
Для этих целей в express.js есть класс Router. Перепишем наше приложение, чтобы использовать роутеры:
Создайте каталог routes в котором будем создавать отдельные файлы для сущностей
Создайте файл routes/menu_item.js, добавьте в него обвязку для роутера и перенесите в него конечные точки для модели menu-item:
const express = require('express')
// создаем экземпляр роутера и экспортируем его из модуля
const router = express.Router()
module.exports = router
// описываем конечные точки роутера
// обратите внимание - начало пути (/api/menu-item) мы тут не пишем
// тип параметра задаем используя regex-формат
router.get('/:id(\\d+)', async (req, res) => {
...
})
В файле app.js подключаем созданные роутеры
// импортируем маршруты
const menuItemRouter = require('./routes/menu_item')
const app = express()
// цепляем все запросы начинающиеся с "/api/menu-item" с маршруту
app.use('/api/menu-item', menuItemRouter)
...
Таким образом мы не только обеспечим описание типа переменных пути запроса, но и вынем однотипные конечные точки в разные файлы.
Для того, чтобы тело запроса автоматически преобразовывалось в объект body нужно после создания экземпляра приложения подключить middleware
express.json()(middleware это функции, которые выполняются до обработки endpoints, используются для служебных целей, как в нашем случае, и в целях авторизации. Позже добавим jwt-авторизацию и напишем для неё middleware):
...
const app = express()
app.use(express.json())
...
Используется старый синтаксис без роутера, но вы пишите сразу правильно
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()
}
})
Используется метод get. Пример приводить не буду, он аналогичен запросу списка блюд
Для редактирования в REST используются методы PUT или PATCH. PUT - полная перезапись (замещение объекта), PATCH - частичная перезапись (изменение части объекта)
Мы будем только менять количество, поэтому используем PATCH
// в пути можно описать параметр (поставив знак ":")
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()
}
})
Тут ничего нового
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 всех моделей) для своего курсового проекта по образцу.