# Обрабатываем ошибки в express и mongoose

poster
В этом видео мы с вами разберем, как обрабатывать ошибки в express и mongoose правильно.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

В этом видео мы с вами разберем, как обрабатывать ошибки в 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, как и должно быть.

Обрабатывая ошибки таким образом мы не повторяем код переносим ответственность с роутов на отдельный хендлер.

Если у вас возникли какие-то вопросы или комментарии, пишите их прямо под этим видео.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Igor Bond
6 лет назад
Подскажите плиз а как обрабатывать ошибки валидации?
Igor Bond
6 лет назад
Хотя может вопрос в другом - если на клиенте на Ангуляре происходит валидация полей, обязательно ли с сервера возвращать какая именно была ошибка валидации (к примеру поле обязательное, или формат мыла не соответствует), или все таки достаточно кинуть 500 ошибку, ибо такая ошибка на сервере возможна только если запрос был отправлен в обход клиента.
monsterlessons
6 лет назад
Обычно все ошибки реализуют на сервере. Для удобства клиента некоторые из них приходится дублировать на клиенте, если вы хотите их показать до отправки данных серверу. Сервер может вам возвращать любые данные и статусы какие вы хотите, но стоит хотя бы возвращать корректные статусы на определенные ошибки. Вы можете почитать больше тут https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP
Igor Bond
6 лет назад
Основные ошибки то понятно - типа 404, или 500. А вот сами ошибки валидации полей формы. К примеру есть 5 полей, каждое обязательное, минимум 5 символов, максимум 10 символов, у каждого поля есть регулярка и некоторые из полей должны быть уникальны. Mongoose по каждому условию со схемы выдает ошибку и не сохраняет невалидные данные. Но нужно ли по каждой ошибке делать обработку, или все таки с сервера возвращать общую ошибку? Ведь получается что если у меня на клиенте есть валидация, и приложение априори без js работать вообще не будет - и если на сервер пришли невалидные данные - значит они были умышленно отправлены в обход валидации на клиенте.
monsterlessons
6 лет назад
Вы можете возвращать как общую ошибку, так и ошибки для каждого поля. Все зависит от вашего UI и как именно вы обрабатываете ошибки на клиенте. Здесь нет единого правильного решения.
Igor Bond
6 лет назад
Подскажите плиз еще такой вопрос - на бекэнде есть добавление юзера, логин должен быть уникальным. Я видел несколько примеров регистрации - при добавлении юзера сначала делают запрос на проверку есть ли юзер с таким логином, а потом уже добавляют. Но ведь в схеме можно указать unique: true, и если такой логин уже есть то новый юзер не добавляется и не нужно плодить лишний запрос. Можно ли положится на такую проверку, или все таки делать отдельный запрос?
monsterlessons
6 лет назад
unique:true достаточно для проверки.
Moe Green
6 лет назад
Круто! А что за загадочный для меня значок тильды в коде?
monsterlessons
6 лет назад
Побитовый оператор. В данном случае ~err.message.indexOf('Cast to ObjectId failed') проверяет есть ли подстрока в строке. Больше можете почитать тут https://stackoverflow.com/questions/12299665/what-does-a-tilde-do-when-it-precedes-an-expression P.S. В реальных проектах лучше не использовать конечно. _.contains или _.includes более human readable.
Moe Green
6 лет назад
благодарю )
Moe Green
6 лет назад
я так понимаю, что в последней версии lodash метода _.contains больше нет? есть только метод _.includes?
Moe Green
6 лет назад
кстати - а почему Вы пользуетесь yarn? По-моему, npm v.5 ничем не хуже yarn на данный момент?
monsterlessons
6 лет назад
Именно. Как и все, они тоже любят отламывать API в новым версиях (for no reason).
monsterlessons
6 лет назад
В npm 5 до сих пор куча открытых issue, которые часто встречаются. Мне не нравится скорость их разработки и странные решения, которые они принимают. Например, что при существовании lock файла пакет может обновить минорную пакета.
Moe Green
6 лет назад
интересно ) не знал о таком факте
Alex
6 лет назад
Спасибо за очередную интересную тему! как раз сейчас работаю с проектом, где есть mongodb и хотя обработка ошибок уже была встроена, не хватало понимания, почему именно так все работает, а в данном топике как раз есть объяснение Одно из предложений было сложно понять : "Так как при миддл вары идут сверху вниз, то после всех наших роутов мы можем добавить его..." Возможно, имеется ввиду, что обработка миддлваров идет сверху вниз?
Alex
6 лет назад
+ интересно, почему выбрано обрезание текста на полуслове в комментариях... http://prntscr.com/hcby84
monsterlessons
6 лет назад
На здоровье. Да, конечно, обработка миддлваров идет сверху вниз) Спасибо, что написали про обрезания слов. Это я так вчера баг фиксил :D. Сейчас вроде исправил нормально.