Магазин на JavaScript, часть 3 из 20. Добавляем классы контроллеров, обработка ошибок

14.11.2021

Теги: BackendExpress.jsFrontendJavaScriptNode.jsORMReact.jsWeb-разработкаБазаДанныхИнтернетМагазинКаталогТоваровКорзинаФреймворк

Добавляем контроллеры

Сейчас функции, которые обрабатывают HTTP-запросы от клиента, расположены прямо в роутере. Но это задача контроллера, которых у нас будет несколько — для товаров, категорий, брендов и пользователей. Создаем директорию controllers и внутри нее — файлы Product.js, Category.js, Brand.js и User.js.

class Product {
    async getAll(req, res) {
        res.status(200).send('Список всех товаров')
    }

    async getOne(req, res) {
        res.status(200).send('Получение одного товара')
    }

    async create(req, res) {
        res.status(200).send('Создание нового товара')
    }

    async update(req, res) {
        res.status(200).send('Обновление товара')
    }

    async delete(req, res) {
        res.status(200).send('Удаление товара')
    }
}

export default new Product()
class Category {
    async getAll(req, res) {
        res.status(200).send('Список всех категорий')
    }

    async getOne(req, res) {
        res.status(200).send('Получение одной категории')
    }

    async create(req, res) {
        res.status(200).send('Создание новой категории')
    }

    async update(req, res) {
        res.status(200).send('Обновление категории')
    }

    async delete(req, res) {
        res.status(200).send('Удаление категории')
    }
}

export default new Category()
class Brand {
    async getAll(req, res) {
        res.status(200).send('Список всех брендов')
    }

    async getOne(req, res) {
        res.status(200).send('Получение одного бренда')
    }

    async create(req, res) {
        res.status(200).send('Создание нового бренда')
    }

    async update(req, res) {
        res.status(200).send('Обновление бренда')
    }

    async delete(req, res) {
        res.status(200).send('Удаление бренда')
    }
}

export default new Brand()
class User {
    async signup(req, res) {
        res.status(200).send('Регистрация пользователя')
    }

    async login(req, res) {
        res.status(200).send('Вход в личный кабинет')
    }

    async check(req, res) {
        res.status(200).send('Проверка авторизации')
    }

    async getAll(req, res) {
        res.status(200).send('Список всех пользователей')
    }

    async getOne(req, res) {
        res.status(200).send('Получение одного пользователя')
    }

    async create(req, res) {
        res.status(200).send('Создание нового пользователя')
    }

    async update(req, res) {
        res.status(200).send('Обновление пользователя')
    }

    async delete(req, res) {
        res.status(200).send('Удаление пользователя')
    }
}

export default new User()

Теперь удаляем из маршрутов функции, а вместо них указываем методы контроллеров, которые будут обрабатывать HTTP-запросы.

import express from 'express'
import ProductController from '../controllers/Product.js'

const router = new express.Router()

router.get('/getall', ProductController.getAll)
router.get('/getone/:id([0-9]+)', ProductController.getOne)
router.post('/create', ProductController.create)
router.put('/update/:id([0-9]+)', ProductController.update)
router.delete('/delete/:id([0-9]+)', ProductController.delete)

export default router
import express from 'express'
import CategoryController from '../controllers/Category.js'

const router = new express.Router()

router.get('/getall', CategoryController.getAll)
router.get('/getone/:id([0-9]+)', CategoryController.getOne)
router.post('/create', CategoryController.create)
router.put('/update/:id([0-9]+)', CategoryController.update)
router.delete('/delete/:id([0-9]+)', CategoryController.delete)

export default router
import express from 'express'
import BrandController from '../controllers/Brand.js'

const router = new express.Router()

router.get('/getall', BrandController.getAll)
router.get('/getone/:id([0-9]+)', BrandController.getOne)
router.post('/create', BrandController.create)
router.put('/update/:id([0-9]+)', BrandController.update)
router.delete('/delete/:id([0-9]+)', BrandController.delete)

export default router
import express from 'express'
import UserController from '../controllers/User.js'

const router = new express.Router()

router.post('/signup', UserController.signup)
router.post('/login', UserController.login)
router.get('/check', UserController.check)

router.get('/getall', UserController.getAll)
router.get('/getone/:id([0-9]+)', UserController.getOne)
router.post('/create', UserController.create)
router.put('/update/:id([0-9]+)', UserController.update)
router.delete('/delete/:id([0-9]+)', UserController.delete)

export default router

Обработка ошибок

Создаем директорию errors, а внутри нее — файл AppError.js, где опишем класс, расширяющий Error.

class AppError extends Error{
    constructor(status, message) {
        super();
        this.status = status
        this.message = message
    }

    static badRequest(message) {
        return new AppError(404, message)
    }

    static internalServerError(message) {
        return new AppError(500, message)
    }

    static forbidden(message) {
        return new AppError(403, message)
    }
}

export default AppError

Теперь создаем директорию middleware, а внутри нее файл ErrorHandler.js с функцией ErrorHandler.

import AppError from '../errors/AppError.js'

const ErrorHandler = (err, req, res, next) => {
    if (err instanceof AppError) {
        return res.status(err.status).json({message: err.message})
    }
    return res.status(500).json({message: 'Непредвиденная ошибка'})
}

export default ErrorHandler

Этот middleware будем использовать в index.js. Этот middleware должен быть последним и в нем не должно быть вызова next().

/* .......... */
import ErrorHandler from './middleware/ErrorHandler.js'

const PORT = process.env.PORT || 5000

const app = express()
app.use(cors())
app.use(express.json())
app.use('/api', router)

// обработка ошибок
app.use(ErrorHandler)

const start = async () => {
    /* .......... */
}

start()

Давайте проверим, как это работает. Допустим, что для получения списка всех товаров был отправлен некорректный HTTP-запрос.

import AppError from '../errors/AppError.js'

class Product {
    async getAll(req, res, next) {
        if (true) {
            return next(AppError.badRequest('Этот запрос не удалось обработать'))
        }
        res.status(200).send('Список всех товаров')
    }
    /* .......... */
}

export default new Product()

С помощью расширения REST Client для VS Code отправим запрос на получение списка товаров и посмотрим на ответ сервера:

Поиск: Backend • Express.js • Frontend • JavaScript • Node.js • ORM • React.js • Web-разработка • База данных • Интернет магазин • Каталог товаров • Корзина • Фреймворк

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.