Магазин на JavaScript, часть 4 из 19. CRUD для категорий и товаров, загрузка изображений
15.11.2021
Теги: Backend • Express.js • Frontend • JavaScript • Node.js • ORM • React.js • Web-разработка • БазаДанных • ИнтернетМагазин • КаталогТоваров • Корзина • Фреймворк
Хорошо, теперь нам нужно реализовать все методы всех контроллеров, чтобы можно было создавать, обновлять, удалять и получать товары, категории, бренды и пользователей. Здесь нам поможет документация ORM-библиотеки Sequelize и ее методы create
, update
, save
, destroy
, reload
, findOne
, findByPk
, findAll
.
import { Product as ProductMapping } from '../models/mapping.js' import AppError from '../errors/AppError.js' import FileService from '../services/File.js' class Product { async getAll(req, res, next) { try { const products = await ProductMapping.findAll() res.json(products) } catch(e) { next(AppError.badRequest(e.message)) } } async getOne(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id товара') } const product = await ProductMapping.findByPk(req.params.id) if (!product) { throw new Error('Товар не найден в БД') } res.json(product) } catch(e) { next(AppError.badRequest(e.message)) } } async create(req, res, next) { try { // поскольку image не допускает null, задаем пустую строку const {name, price, image = '', categoryId = null, brandId = null} = req.body const product = await ProductMapping.create({name, price, image, categoryId, brandId}) res.json(product) } catch(e) { next(AppError.badRequest(e.message)) } } async update(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id товара') } const product = await ProductMapping.findByPk(req.params.id) if (!product) { throw new Error('Товар не найден в БД') } const name = req.body.name ?? product.name const price = req.body.price ?? product.price await product.update({name, price}) res.json(product) } catch(e) { next(AppError.badRequest(e.message)) } } async delete(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id товара') } const product = await ProductMapping.findByPk(req.params.id) if (!product) { throw new Error('Товар не найден в БД') } await product.destroy() res.json(product) } catch(e) { next(AppError.badRequest(e.message)) } } } export default new Product()
Прежде, чем двигаться дальше — создадим один товар, получим его из БД по идентификатору, потом обновим, а затем — удалим. Для этого редактируем файл test.http
:
### Список всех товаров GET /api/product/getall HTTP/1.1 Host: localhost:7000 ### Получить один товар GET /api/product/getone/1 HTTP/1.1 Host: localhost:7000 ### Создать новый товар POST /api/product/create HTTP/1.1 Host: localhost:7000 Content-type: application/json; charset=utf-8 { "name": "Первый товар", "price": 11111 } ### Обновить товар PUT /api/product/update/1 HTTP/1.1 Host: localhost:7000 Content-type: application/json; charset=utf-8 { "name": "Первый товар (обновление)", "price": 22222 } ### Удалить товар DELETE /api/product/delete/1 HTTP/1.1 Host: localhost:7000
Двигаемся дальше — реализуем методы контроллеров для категорий, брендов и пользователей.
import { Category as CategoryMapping } from '../models/mapping.js' import AppError from '../errors/AppError.js' class Category { async getAll(req, res, next) { try { const categories = await CategoryMapping.findAll() res.json(categories) } catch(e) { next(AppError.badRequest(e.message)) } } async getOne(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id категории') } const category = await CategoryMapping.findByPk(req.params.id) if (!category) { throw new Error('Категория не найдена в БД') } res.json(category) } catch(e) { next(AppError.badRequest(e.message)) } } async create(req, res, next) { try { const category = await CategoryMapping.create({name: req.body.name}) res.json(category) } catch(e) { next(AppError.badRequest(e.message)) } } async update(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id категории') } const category = await CategoryMapping.findByPk(req.params.id) if (!category) { throw new Error('Категория не найдена в БД') } const name = req.body.name ?? category.name await category.update({name}) res.json(category) } catch(e) { next(AppError.badRequest(e.message)) } } async delete(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id категории') } const category = await CategoryMapping.findByPk(req.params.id) if (!category) { throw new Error('Категория не найдена в БД') } await category.destroy() res.json(category) } catch(e) { next(AppError.badRequest(e.message)) } } } export default new Category()
import { Brand as BrandMapping } from '../models/mapping.js' import AppError from '../errors/AppError.js' class Brand { async getAll(req, res, next) { try { const brands = await BrandMapping.findAll() res.json(brands) } catch(e) { next(AppError.badRequest(e.message)) } } async getOne(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id бренда') } const brand = await BrandMapping.findByPk(req.params.id) if (!brand) { throw new Error('Бренд не найден в БД') } res.json(brand) } catch(e) { next(AppError.badRequest(e.message)) } } async create(req, res, next) { try { const brand = await BrandMapping.create({name: req.body.name}) res.json(brand) } catch(e) { next(AppError.badRequest(e.message)) } } async update(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id бренда') } const brand = await BrandMapping.findByPk(req.params.id) if (!brand) { throw new Error('Бренд не найден в БД') } const name = req.body.name ?? brand.name await brand.update({name}) res.json(brand) } catch(e) { next(AppError.badRequest(e.message)) } } async delete(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id бренда') } const brand = await BrandMapping.findByPk(req.params.id) if (!brand) { throw new Error('Бренд не найден в БД') } await brand.destroy() res.json(brand) } catch(e) { next(AppError.badRequest(e.message)) } } } export default new Brand()
import { User as UserMapping } from '../models/mapping.js' import AppError from '../errors/AppError.js' class User { async signup(req, res, next) { res.status(200).send('Регистрация пользователя') } async login(req, res, next) { res.status(200).send('Вход в личный кабинет') } async check(req, res, next) { res.status(200).send('Проверка авторизации') } async update(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id пользователя') } const user = await UserMapping.findByPk(req.params.id) if (!user) { throw new Error('Пользователь не найден в БД') } const name = req.body.name ?? user.name const password = req.body.password ?? user.password await user.update({name, password}) res.json(user) } catch(e) { next(AppError.badRequest(e.message)) } } async delete(req, res, next) { try { if (!req.params.id) { throw new Error('Не указан id пользователя') } const user = await UserMapping.findByPk(req.params.id) if (!user) { throw new Error('Пользователь не найден в БД') } await user.destroy() res.json(user) } catch(e) { next(AppError.badRequest(e.message)) } } } export default new User()
Загрузка изображений
Для загрузки изображений нам потребуется пакет express-fileupload
— устанавливаем, импортируем и добавляем middleware в index.js
:
> npm install express-fileupload
import config from 'dotenv/config' import express from 'express' import sequelize from './sequelize.js' import * as mapping from './models/mapping.js' import cors from 'cors' import fileUpload from 'express-fileupload' import router from './routes/index.js' import ErrorHandler from './middleware/ErrorHandler.js' const PORT = process.env.PORT || 5000 const app = express() // Cross-Origin Resource Sharing app.use(cors()) // middleware для работы с json app.use(express.json()) // middleware для загрузки файлов app.use(fileUpload()) // все маршруты приложения app.use('/api', router) // обработка ошибок app.use(ErrorHandler) const start = async () => { try { await sequelize.authenticate() await sequelize.sync() app.listen(PORT, () => console.log('Сервер запущен на порту', PORT)) } catch(e) { console.log(e) } } start()
Давайте посмотрим, какие данные нам будут доступны при отправке POST-запроса с изображением на создание нового товара. Для этого выводим в консоль req.body
и req.files
.
import { Product as ProductMapping } from '../models/mapping.js' import AppError from '../errors/AppError.js' class Product { /* .......... */ async create(req, res, next) { console.log(req.body) console.log(req.files) /* .......... */ } /* .......... */ } export default new Product()
Теперь подготовим POST-запрос — редактируем test.http
и кладем в директорию shop/server
файл picture.jpg
:
### создание товара с изображением POST /api/product/create HTTP/1.1 Host: localhost:7000 Content-Type: multipart/form-data; boundary=MultiPartFormDataBoundary --MultiPartFormDataBoundary Content-Disposition: form-data; name="name" Название товара --MultiPartFormDataBoundary Content-Disposition: form-data; name="price" 11111 --MultiPartFormDataBoundary Content-Disposition: form-data; name="image"; filename="picture.jpg" Content-Type: image/jpeg < ./picture.jpg --MultiPartFormDataBoundary--
Сервер запущен на порту 7000 { name: 'Название товара', price: '11111' } { image: { name: 'picture.jpg', data: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 00 60 00 60 00 00 ff db 00 43 00 ... 408440 more bytes>, size: 408490, encoding: '7bit', tempFilePath: '', truncated: false, mimetype: 'image/jpeg', md5: '0b37ad42c4efda0dc188762d72f63ae8', mv: [Function: mv] } }
У нас есть размер, контрольная сумма, mimetype
— а главное, есть функция mv()
, с помощью которой можно поместить изображение в какую-то директорию проекта. Давайте сразу создадим такую директорию — пусть это будет shop/server/static
. Для загрузки изображения создадим класс File
в директории shop/server/services
.
import * as uuid from 'uuid' import * as path from 'path' class File { save(file) { if (!file) return null const [, ext] = file.mimetype.split('/') const fileName = uuid.v4() + '.' + ext const filePath = path.resolve('static', fileName) file.mv(filePath) return fileName } } export default new File()
Здесь мы используем пакет uuid
для генерации уникального имени файла, так что его надо установить:
> npm install uuid
В контроллере теперь будем использовать этот класс для загрузки изображений и сохранять имя файла в базу данных:
import { Product as ProductMapping } from '../models/mapping.js' import AppError from '../errors/AppError.js' import FileService from '../services/File.js' class Product { /* .......... */ async create(req, res, next) { try { // поскольку image не допускает null, задаем пустую строку const image = FileService.save(req.files?.image) ?? '' const {name, price, categoryId = null, brandId = null} = req.body const product = await ProductMapping.create({name, price, image, categoryId, brandId}) res.json(product) } catch(e) { next(AppError.badRequest(e.message)) } } /* .......... */ } export default new Product()
Чтобы изображение можно было увидеть в браузере, добавляем еще одно middleware в файле index.js
:
/* .......... */ const app = express() // Cross-Origin Resource Sharing app.use(cors()) // middleware для работы с json app.use(express.json()) // middleware для статики (img, css) app.use(express.static('static')) // middleware для загрузки файлов app.use(fileUpload()) // все маршруты приложения app.use('/api', router) /* .......... */
Теперь можно открыть загруженное изображение в браузере http://localhost:7000/9f7af171-f971-4b41-8e2b-d61737e5a2f4.jpeg
.
- Магазин на JavaScript, часть 19 из 19. Редактирование характеристик и рефакторинг приложения
- Магазин на JavaScript, часть 18 из 19. Панель управления: редактирование категорий и брендов
- Магазин на JavaScript, часть 17 из 19. Панель управления: список заказов, категорий и брендов
- Магазин на JavaScript, часть 15 из 19. Работа с заказами на сервере, оформление заказа
- Магазин на JavaScript, часть 14 из 19. Кнопка «Назад», страница товара, корзина покупателя
- Магазин на JavaScript, часть 13 из 19. Хранилище каталога, компонент витрины, кнопка «Назад»
- Магазин на JavaScript, часть12 из 19. Запросы на сервер, состояние приложения, Signup и Login
Поиск: Backend • Express.js • Frontend • JavaScript • Node.js • ORM • React.js • Web-разработка • База данных • Интернет магазин • Каталог товаров • Корзина • Фреймворк