В этом видео мы с вами разберем, как обрабатывать ошибки в express и mongoose правильно.
Давайте попробуем. На данный момент у нас есть index файл с подключением к mongodb и стартом сервера и модель user, также у нас есть роут, где мы выгребаем юзера из базы.
Так как наш index.js начал разрастаться, то давайте создадим файл routes.js, где будет хранить все роуты, и их обработку.
const User = require('./models/user')
module.exports = app => {
app.get('/user', (req, res) => {
User.findUserByName('alex', (err, user) => {
res.json(user)
})
})
}
Так, как мы сделали routes функцией, которая принимает app, то нам нужно вызвать ее в index.js
const routes = require('./routes')
routes(app)
Если мы посмотрим в браузер, то все по прежнему работает, как и раньше.
Теперь, давайте добавим поиск юзера по id. У нас будет url /users/id. И по имени, который приходит параметром мы будем выгребать данные.
app.get('/users/:id', (req, res) => {
User.findById(req.params.id, (err, user) => {
console.log(err, user)
res.json(user)
})
})
Для того, чтобы прочитать id из параметров мы можем использовать req.params.id.
Если мы посмотрим в браузер, то все работает.
Теперь давайте посмотрим, что произойдет, если мы передадим невалидный id в качестве параметра.
В консоли мы получаем ошибку
CastError: Cast to ObjectId failed for value "111" at path "_id" for model "User"
Эта ошибка вызывает недоумение у всех, кто начинает работать с mongoose. Вместо того, чтобы просто вернуть null, показав что записи с таким id нет, как делают большинство других библиотек мы получаем ошибку, что mongoose не может привести строку 111 в ObjectId.
Поэтому нам нужно самим обработать, что если у нас в ошибке есть текст Cast to ObjectId failed, то мы хотим вернуть 404.
User.findById(req.params.id, (err, user) => {
if (err.message && ~err.message.indexOf('Cast to ObjectId failed')) {
res.sendStatus(404)
}
res.json(user)
})
Если мы посмотрим в браузер, то мы получили not found, как нам и нужно.
А теперь представьте, что нам в каждом роуте нужно хендлить ошибки. Причем не только 404, но и 500. И конечно, это очень много кода и не очень удобно.
Хорошей практикой является обработка общих ошибок в одном месте. Для этого в express есть понятие next. Это значит передать выполнение следующей middleware. Давайте если у нас происходит в роуте любая ошибка, то мы вызываем next и обрабатываем ошибки в нашем роутере.
app.get('/users/:id', (req, res, next) => {
User.findById(req.params.id, (err, user) => {
if (err) {
next(err)
}
res.json(user)
})
})
Теперь у нас нет никакого обработчика ошибок в этом методе, а мы просто передает ошибку дальше и надеемся, что ее дальше какая-то миддлвара обработает.
Теперь нам нужно добавить общий обработчик ошибок в роутере. Так как при миддл вары идут сверху вниз, то после всех наших роутов мы можем добавить его и тогда из всех роутов при вызове next ошибки будут проходить дальше.
Если у нас происходит 500 ошибка, то мы хотим вывести в консоль и на экран stacktrace.
app.use((err, req, res, next) => {
console.log(err.stack)
res.status(500).json({error: err.stack})
})
Теперь если мы например сделаем throw Error, то наш хендлер должен сработать.
throw new Error('some magic error')
Как мы видим, мы получили в браузере вывод ошибки, что очень удобно для дебага.
Теперь мы хотим, чтобы 404 ошибки обрабатывались блоком ниже, но для этого нам нужно в этом блоке вызвать для них next. Нам нужно это, чтобы одновременно обработать 404 для всех несуществующих урлов и Cast ошибку. Поэтому 404 должен всегда идти последним.
app.use((err, req, res, next) => {
const isNotFound = ~err.message.indexOf('not found')
const isCastError = ~err.message.indexOf('Cast to ObjectId failed')
if (err.message && (isNotFound || isCastError)) {
return next()
}
console.log(err.stack)
res.status(500).json({error: err.stack})
})
app.use((req, res) => {
res.sendStatus(404)
})
Теперь если мы указываем несуществующий id, у нас выводится 404, как и должно быть.
Обрабатывая ошибки таким образом мы не повторяем код переносим ответственность с роутов на отдельный хендлер.
Если у вас возникли какие-то вопросы или комментарии, пишите их прямо под этим видео.