Express отправить ошибку

Error Handling refers to how Express catches and processes errors that
occur both synchronously and asynchronously. Express comes with a default error
handler so you don’t need to write your own to get started.

Catching Errors

It’s important to ensure that Express catches all errors that occur while
running route handlers and middleware.

Errors that occur in synchronous code inside route handlers and middleware
require no extra work. If synchronous code throws an error, then Express will
catch and process it. For example:

app.get('/', (req, res) => {
  throw new Error('BROKEN') // Express will catch this on its own.
})

For errors returned from asynchronous functions invoked by route handlers
and middleware, you must pass them to the next() function, where Express will
catch and process them. For example:

app.get('/', (req, res, next) => {
  fs.readFile('/file-does-not-exist', (err, data) => {
    if (err) {
      next(err) // Pass errors to Express.
    } else {
      res.send(data)
    }
  })
})

Starting with Express 5, route handlers and middleware that return a Promise
will call next(value) automatically when they reject or throw an error.
For example:

app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

If getUserById throws an error or rejects, next will be called with either
the thrown error or the rejected value. If no rejected value is provided, next
will be called with a default Error object provided by the Express router.

If you pass anything to the next() function (except the string 'route'),
Express regards the current request as being an error and will skip any
remaining non-error handling routing and middleware functions.

If the callback in a sequence provides no data, only errors, you can simplify
this code as follows:

app.get('/', [
  function (req, res, next) {
    fs.writeFile('/inaccessible-path', 'data', next)
  },
  function (req, res) {
    res.send('OK')
  }
])

In the above example next is provided as the callback for fs.writeFile,
which is called with or without errors. If there is no error the second
handler is executed, otherwise Express catches and processes the error.

You must catch errors that occur in asynchronous code invoked by route handlers or
middleware and pass them to Express for processing. For example:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})

The above example uses a try...catch block to catch errors in the
asynchronous code and pass them to Express. If the try...catch
block were omitted, Express would not catch the error since it is not part of the synchronous
handler code.

Use promises to avoid the overhead of the try...catch block or when using functions
that return promises. For example:

app.get('/', (req, res, next) => {
  Promise.resolve().then(() => {
    throw new Error('BROKEN')
  }).catch(next) // Errors will be passed to Express.
})

Since promises automatically catch both synchronous errors and rejected promises,
you can simply provide next as the final catch handler and Express will catch errors,
because the catch handler is given the error as the first argument.

You could also use a chain of handlers to rely on synchronous error
catching, by reducing the asynchronous code to something trivial. For example:

app.get('/', [
  function (req, res, next) {
    fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
      res.locals.data = data
      next(err)
    })
  },
  function (req, res) {
    res.locals.data = res.locals.data.split(',')[1]
    res.send(res.locals.data)
  }
])

The above example has a couple of trivial statements from the readFile
call. If readFile causes an error, then it passes the error to Express, otherwise you
quickly return to the world of synchronous error handling in the next handler
in the chain. Then, the example above tries to process the data. If this fails then the
synchronous error handler will catch it. If you had done this processing inside
the readFile callback then the application might exit and the Express error
handlers would not run.

Whichever method you use, if you want Express error handlers to be called in and the
application to survive, you must ensure that Express receives the error.

The default error handler

Express comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.

If you pass an error to next() and you do not handle it in a custom error
handler, it will be handled by the built-in error handler; the error will be
written to the client with the stack trace. The stack trace is not included
in the production environment.

Set the environment variable NODE_ENV to production, to run the app in production mode.

When an error is written, the following information is added to the
response:

  • The res.statusCode is set from err.status (or err.statusCode). If
    this value is outside the 4xx or 5xx range, it will be set to 500.
  • The res.statusMessage is set according to the status code.
  • The body will be the HTML of the status code message when in production
    environment, otherwise will be err.stack.
  • Any headers specified in an err.headers object.

If you call next() with an error after you have started writing the
response (for example, if you encounter an error while streaming the
response to the client) the Express default error handler closes the
connection and fails the request.

So when you add a custom error handler, you must delegate to
the default Express error handler, when the headers
have already been sent to the client:

function errorHandler (err, req, res, next) {
  if (res.headersSent) {
    return next(err)
  }
  res.status(500)
  res.render('error', { error: err })
}

Note that the default error handler can get triggered if you call next() with an error
in your code more than once, even if custom error handling middleware is in place.

Writing error handlers

Define error-handling middleware functions in the same way as other middleware functions,
except error-handling functions have four arguments instead of three:
(err, req, res, next). For example:

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

You define error-handling middleware last, after other app.use() and routes calls; for example:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
  // logic
})

Responses from within a middleware function can be in any format, such as an HTML error page, a simple message, or a JSON string.

For organizational (and higher-level framework) purposes, you can define
several error-handling middleware functions, much as you would with
regular middleware functions. For example, to define an error-handler
for requests made by using XHR and those without:

const bodyParser = require('body-parser')
const methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

In this example, the generic logErrors might write request and
error information to stderr, for example:

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

Also in this example, clientErrorHandler is defined as follows; in this case, the error is explicitly passed along to the next one.

Notice that when not calling “next” in an error-handling function, you are responsible for writing (and ending) the response. Otherwise those requests will “hang” and will not be eligible for garbage collection.

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

Implement the “catch-all” errorHandler function as follows (for example):

function errorHandler (err, req, res, next) {
  res.status(500)
  res.render('error', { error: err })
}

If you have a route handler with multiple callback functions you can use the route parameter to skip to the next route handler. For example:

app.get('/a_route_behind_paywall',
  (req, res, next) => {
    if (!req.user.hasPaid) {
      // continue handling this request
      next('route')
    } else {
      next()
    }
  }, (req, res, next) => {
    PaidContent.find((err, doc) => {
      if (err) return next(err)
      res.json(doc)
    })
  })

In this example, the getPaidContent handler will be skipped but any remaining handlers in app for /a_route_behind_paywall would continue to be executed.

Calls to next() and next(err) indicate that the current handler is complete and in what state. next(err) will skip all remaining handlers in the chain except for those that are set up to handle errors as described above.

Когда я только начинал работать с Express и пытался разобраться с тем, как обрабатывать ошибки, мне пришлось нелегко. Возникало такое ощущение, будто никто не писал о том, что мне было нужно. В итоге мне пришлось самому искать ответы на мои вопросы. Сегодня я хочу рассказать всё, что знаю об обработке ошибок в Express-приложениях. Начнём с синхронных ошибок.

Обработка синхронных ошибок

Если вам нужно обработать синхронную ошибку, то вы можете, для начала, с помощью инструкции throw, выдать такую ошибку в обработчике запроса Express. Обратите внимание на то, что обработчики запросов ещё называют «контроллерами», но я предпочитаю использовать термин «обработчик запросов» так как он кажется мне понятнее.

Вот как это выглядит:

app.post('/testing', (req, res) => {
  throw new Error('Something broke! ')
})

Такие ошибки можно перехватить с помощью обработчика ошибок Express. Если вы не написали собственный обработчик ошибок (подробнее об этом мы поговорим ниже), то Express обработает ошибку с помощью обработчика, используемого по умолчанию.

Вот что делает стандартный обработчик ошибок Express:

  1. Устанавливает код состояния HTTP-ответа в значение 500.
  2. Отправляет сущности, выполнившей запрос, текстовый ответ.
  3. Логирует текстовый ответ в консоль.

Сообщение об ошибке, выведенное в консоль

Обработка асинхронных ошибок

Для обработки асинхронных ошибок нужно отправить ошибку обработчику ошибок Express через аргумент next:

app.post('/testing', async (req, res, next) => {
  return next(new Error('Something broke again! '))
})

Вот что попадёт в консоль при логировании этой ошибки.

Сообщение об ошибке, выведенное в консоль

Если вы пользуетесь в Express-приложении конструкцией async/await, то вам понадобится использовать функцию-обёртку, наподобие express-async-handler. Это позволяет писать асинхронный код без блоков try/catch. Подробнее об async/await в Express можно почитать здесь.

const asyncHandler = require('express-async-handler')

app.post('/testing', asyncHandler(async (req, res, next) => {
  // Сделать что-нибудь
}))

После того, как обработчик запроса обёрнут в express-async-handler, то можно, так же, как было описано выше, выбросить ошибку с использованием инструкции throw. Эта ошибка попадёт к обработчику ошибок Express.

app.post('/testing', asyncHandler(async (req, res, next) => {
  throw new Error('Something broke yet again! ')
}))

Сообщение об ошибке, выведенное в консоль

Написание собственного обработчика ошибок

Обработчики ошибок Express принимают 4 аргумента:

  1. error
  2. req
  3. res
  4. next

Размещать их нужно после промежуточных обработчиков и маршрутов.

app.use(/*...*/)
app.get(/*...*/)
app.post(/*...*/)
app.put(/*...*/)
app.delete(/*...*/)

// Собственный обработчик ошибок нужно поместить после всех остальных промежуточных обработчиков
app.use((error, req, res, next) => { /* ... */ })

Если создать собственный обработчик ошибок, то Express прекратит использование стандартного обработчика. Для того чтобы обработать ошибку, нужно сформировать ответ для фронтенд-приложения, которое обратилось к конечной точке, в которой возникла ошибка. Это означает, что нужно выполнить следующие действия:

  1. Сформировать и отправить подходящий код состояния ответа.
  2. Сформировать и отправить подходящий ответ.

То, какой именно код состояния подойдёт в каждом конкретном случае, зависит от того, что именно произошло. Вот список типичных ошибок, к обработке которых вы должны быть готовы:

  1. Ошибка 400 Bad Request. Используется в двух ситуациях. Во-первых — тогда, когда пользователь не включил в запрос необходимое поле (например — в отправленной платёжной форме не заполнено поле со сведениями о кредитной карте). Во-вторых — тогда, когда в запросе содержатся некорректные данные (например — ввод в поле пароля и в поле подтверждения пароля разных паролей).
  2. Ошибка 401 Unauthorized. Этот код состояния ответа применяется в том случае, если пользователь ввёл неправильные учётные данные (вроде имени пользователя, адреса электронной почты или пароля).
  3. Ошибка 403 Forbidden. Используется в тех случаях, когда пользователю не разрешён доступ к конечной точке.
  4. Ошибка 404 Not Found. Применяется в тех случаях, когда конечную точку невозможно обнаружить.
  5. Ошибка 500 Internal Server Error. Применяется тогда, когда запрос, отправленный фронтендом, сформирован правильно, но на бэкенде при этом возникла какая-то ошибка.

После того, как определён подходящий код состояния ответа, его нужно установить с помощью res.status:

app.use((error, req, res, next) => {
  // Ошибка, выдаваемая в ответ на неправильно сформированный запрос
  res.status(400)
  res.json(/* ... */)
})

Код состояния ответа должен соответствовать сообщению об ошибке. Для этого нужно отправлять код состояния вместе с ошибкой.

Легче всего это сделать с помощью пакета http-errors. Он позволяет отправлять в ошибке три фрагмента информации:

  1. Код состояния ответа.
  2. Сообщение, сопутствующее ошибке.
  3. Любые данные, которые нужно отправить (это необязательно).

Вот как установить пакет http-errors:

npm install http-errors --save

Вот как этим пакетом пользоваться:

const createError = require('http-errors')

// Создание ошибки
throw createError(status, message, properties)

Рассмотрим пример, который позволит как следует в этом всём разобраться.

Представим, что мы пытаемся обнаружить пользователя по адресу его электронной почты. Но этого пользователя найти не удаётся. В результате мы решаем отправить в ответ на соответствующий запрос ошибку User not found, сообщающую вызывающей стороне о том, что пользователь не найден.

Вот что нам нужно будет сделать при создании ошибки:

  1. Установить код состояния ответа как 400 Bad Request (ведь пользователь ввёл неправильные данные). Это будет наш первый параметр.
  2. Отправить вызывающей стороне сообщение наподобие User not found. Это будет второй параметр.

app.put('/testing', asyncHandler(async (req, res) => {
  const { email } = req.body
  const user = await User.findOne({ email })

  // Если пользователь не найден - выбросим ошибку
  if (!user) throw createError(400, `User '${email}' not found`)
}))

Получить код состояния можно с помощью конструкции error.status, а сообщение ошибки — с помощью error.message:

// Логирование ошибки
app.use((error, req, res, next) => {
  console.log('Error status: ', error.status)
  console.log('Message: ', error.message)
})

Результат логирования ошибки в консоли

Затем состояние ответа устанавливают с помощью res.status, а сообщение записывают в res.json:

app.use((error, req, res, next) => {
  // Установка кода состояния ответа
  res.status(error.status)

  // Отправка ответа
  res.json({ message: error.message })
})

Лично я предпочитаю отправлять в подобных ответах код состояния, сообщение и результат трассировки стека. Это облегчает отладку.

app.use((error, req, res, next) => {
  // Установка кода состояния ответа
  res.status(error.status)

  // Отправка ответа
  res.json({
    status: error.status,
    message: error.message,
    stack: error.stack
  })
})

▍Код состояния ответа, используемый по умолчанию

Если источником ошибки не является createError, то у неё не будет свойства status. Вот пример, в котором сделана попытка прочесть несуществующий файл с помощью fs.readFile:

const fs = require('fs')
const util = require('util')

// Преобразуем readFile из функции, использующей коллбэки, в async/await-функцию.
// Подробности об этом смотрите здесь: https://zellwk.com/blog/callbacks-to-promises
const readFilePromise = util.promisify(fs.readFile)

app.get('/testing', asyncHandler(async (req, res, next) => {
  const data = await readFilePromise('some-file')
})

У такого объекта ошибки не будет свойства status:

app.use((error, req, res, next) => {
  console.log('Error status: ', error.status)
  console.log('Message: ', error.message)
})

Результат логирования ошибки в консоли

В подобных случаях можно задать код ошибки, используемый по умолчанию. А именно, речь идёт об ошибке 500 Internal Server Error:

app.use((error, req, res, next) => {
  res.status(error.status || 500)
  res.json({
    status: error.status,
    message: error.message,
    stack: error.stack
  })
})

▍Изменение кода состояния ошибки

Предположим, мы собираемся прочитать некий файл, воспользовавшись данными, предоставленными пользователем. Если такого файла не существует, это значит, что нам нужно выдать ошибку 400 Bad Request. Ведь в том, что файл найти не удаётся, нет вины сервера.

В подобном случае нужно воспользоваться конструкцией try/catch для перехвата исходной ошибки. Затем нужно воссоздать объект ошибки с помощью createError:

app.get('/testing', asyncHandler(async (req, res, next) => {
  try {
    const { file } = req.body
    const contents = await readFilePromise(path.join(__dirname, file))
  } catch (error) {
    throw createError(400, `File ${file} does not exist`)
  }
})

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

Если запрос прошёл через все промежуточные обработчики и маршруты, но так и не был обработан, это означает, что конечная точка, соответствующая такому запросу, не была найдена.

Для обработки ошибок 404 Not Found нужно добавить, между маршрутами и обработчиком ошибок, дополнительный обработчик. Вот как выглядит создание объекта ошибки 404:

// Промежуточные обработчики...
// Маршруты...

app.use((req, res, next) => {
  next(createError(404))
})

// Обработчик ошибок...

Сведения об ошибке

▍Замечания об ошибке ERR_HTTP_HEADERS_SENT

Не впадайте в панику если видите сообщение об ошибке ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client. Она возникает из-за того, что в одном и том же обработчике многократно вызывается метод, устанавливающий заголовки ответа. Вот методы, вызов которых приводит к автоматической установке заголовков ответа:

  1. res.send
  2. res.json
  3. res.render
  4. res.sendFile
  5. res.sendStatus
  6. res.end
  7. res.redirect

Так, например, если вы вызовете методы res.render и res.json в одном и том же обработчике ответа, то вы получите ошибку ERR_HTTP_HEADERS_SENT:

app.get('/testing', (req, res) => {
  res.render('new-page')
  res.json({ message: '¯_(ツ)_/¯' })
})

В результате, в том случае, если вы сталкиваетесь с этой ошибкой, тщательно проверьте код обработчиков ответа и убедитесь в том, что в нём нет ситуаций, в которых вызывается несколько вышеописанных методов.

▍Обработка ошибок и потоковая передача данных

Если что-то идёт не так при потоковой передаче ответа фронтенду, то можно столкнуться с той же самой ошибкой ERR_HTTP_HEADERS_SENT.

В подобном случае обработку ошибок нужно передать стандартным обработчикам. Такой обработчик отправит ошибку и автоматически закроет соединение.

app.use((error, req, res, next) => {
  // Сделать это нужно только в том случае, если ответ передаётся в потоковом режиме
  if (res.headersSent) {
    return next(error)
  }

  // Остальной код обработки ошибок
})

Итоги

Сегодня я рассказал вам всё, что знаю об обработке ошибок в Express. Надеюсь, это поможет вам писать более надёжные Express-приложения.

Уважаемые читатели! Как вы обрабатываете ошибки в своих Node.js-проектах?

Improve Article

Save Article

Like Article

  • Read
  • Discuss
  • Improve Article

    Save Article

    Like Article

    Error handling in Express is referred to as something that handles or processes errors that may come while executing any synchronous code or asynchronous code.

    What do we mean by synchronous or asynchronous code?
    A lot of times, it happens that an operation begins executing but for some reason faces some delay before completion. The common examples of such operations are HTTP requests such as AJAX requests, functions such as setTimeout etc. These operations begin, and then end when the response returns or when the timer ends. While the computer waits for these operations to complete, it moves on busying itself with the next lines of code. It keeps busy, but this causes a significant challenge — any code dependent on previous asynchronous code might be run before that asynchronous code is complete, meaning errors. Have a look below-

    var data = makeAsyncRequest();

    console.log("Data is " + data);

     
    We can say that, when we execute something synchronously, we wait for it to finish before moving on another task. When we execute something asynchronously, we can move on to another task before it finishes. The above problem is solved using callbacks, middleware modules or by using the modern promise and await.

    Catching Errors in Express

    • If synchronous code having route handlers and middleware throws any error, then without any effort and extra work, Express solves it by catching and processing the error without requiring any of our consent. Have a look at the below code –

      app.get('/', function (req, res) {

          throw new Error('Died')

       })

    • If a route handler and middleware invokes asynchronous function which in turn produces some errors, then we have to explicitly pass the error to the next() function, where Express will catch and process them. The following illustration will help you to understand

      app.get('/', function (req, res, next) {

        fs.readFile('/file-is-not-available'

              function (err, data) {

          if (err) {

            next(err) 

          } else {

            res.send(data)

          }

        })

      })

    • Route handlers and middlewares that return a Promise will call next(value) automatically when they reject or throw an error.

      app.get('/user/:id', async function (req, res, next) { 

          var user = await getUserById(req.params.id)    

          res.send(user)

      })

      The await keyword can be used to indicate that the function that follows will be returning a promise, which should be awaited before executing any other dependent code. ‘await’ can only be used inside an async function.
      Next (next) will be called with either the thrown error or the rejected value in case if getUserById throws an error or rejects. If no rejected value is provided, next will be called with a default Error object provided by the Express router. If we pass anything to the next() function (except the string ‘route’), Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions.

    • If a given callback in a sequence provides no data and only errors, then the code can be simplified as –

      app.get('/', [

        function (req, res, next) {

          fs.writeFile('/path-cannot-be-accessed',

                  'data', next)

        },

        function (req, res) {

          res.send('OK')

        }

      ])

      In the above code, next is provided as the callback which runs without caring whether errors comes out or not. If there is no error, then the second handler also runs otherwise express just catches and processes the error.

      Now, look at the example below – 

      app.get('/', function (req, res, next) {

        setTimeout(function () {

          try {

            throw new Error('Died')

          } catch (err) {

            next(err)

          }

        }, 100)

      })

    • As we know, if a route handler and middleware invokes an asynchronous function which in turn produces some errors, then we have to explicitly pass the error to the next() function, where Express will catch and process them. However, in the above code, the error is not the part of the synchronous code, so we can’t simply pass it to the next function. We need to first throw the errors, catch those errors generated by asynchronous code, and then pass it to the Express. For this, we need to use the try..catch block to catch them. If you don’t want to use try and catch, then simply use promises as shown below –

      app.get('/', function (req, res, next) {

        Promise.resolve().then(function () {

          throw new Error('Died')

        }).catch(next)

      })

      Since promises automatically catch both synchronous errors and rejected promises, you can simply provide next as the final catch handler and Express will catch errors, because the catch handler is given the error as the first argument.

    • Default Error Handlers: The default error handler catches the error when we call next and don’t handle it with a custom error handler. If we want to send a different response to the default, we have to write our own error handler. This default error-handling middleware function is added at the end of the middleware function stack. If you pass an error to next() and you do not handle it in a custom error handler, it will be handled by the built-in error handler, the error will be written to the client with the stack trace. The stack trace is not included in the production environment.

      When an error is written, the following information is added automatically to the response:

      • The res.statusCode is set from err.status (or err.statusCode). If this value is outside the 4xx or 5xx range, it will be set to 500.
      • The res.statusMessage is set as per the status code.
      • The body will be the HTML of the status code message when in production environment, otherwise will be err.stack.
      • Any headers specified in an err.headers object.

      If you call next() with an error after you have started writing the response (for example, if you encounter an error while streaming the response to the client) the Express default error handler closes the connection and fails the request.

      So when you add a custom error handler, you must delegate to the default Express error handler, when the headers have already been sent to the client:

      function errorHandler (err, req, res, next) {

        if (res.headersSent) {

          return next(err)

        }

        res.status(500)

        res.render('error', { error: err })

      }

      Note that the default error handler can get triggered if you call next() with an error in your code more than once, even if custom error handling middleware is in place.

      How to write Error handlers?

      The way we declare the middleware functions, in the same way, error handling functions are defined. However, error-handling functions have four arguments instead of three: (err, req, res, next). For example – 

      app.use(function (err, req, res, next) {

        console.error(err.stack)

        res.status(500).send('Something broke!')

      })

      We need to define error-handling middleware last, after other app.use() and routes calls. The example is shown below – 

      app.get('/', (req, res, next) => {

       req.foo = true;

        setTimeout(() => {

          try {

            throw new Error('error');

          }

          catch (ex) {

            next(ex);

          }

        })

      });

      app.use((err, req, res, next) => {

        if (req.foo) {

          res.status(500).send('Fail!');

        }

        else {

          next(err);

        }

      })

      app.use((err, req, res, next) => {

        res.status(500).send('Error!')

      })

    Last Updated :
    03 Jun, 2020

    Like Article

    Save Article

    So in login page I am sending credentials from angular to express through get request.What I wanna do is that if found in database,send response and handle it in angular else if not found in db I want express to send error response and handle it angular error response function but my code isnt working.

    Angular controller:

    myapp.controller('therapist_login_controller', ['$scope', '$localStorage', '$http',
      function($scope, $localStorage, $http) {
        $scope.login = function() {
          console.log($scope.username + $scope.password);
          var data = {
            userid: $scope.username,
            password: $scope.password
          };
          console.log(data);
          $http.post('/api/therapist-login', data)
            .then(
              function(response) {
                // success callback
                console.log("posted successfully");
                $scope.message = "Login succesful";
              },
              function(response) {
                // failure callback,handle error here
                $scope.message = "Invalid username or password"
                console.log("error");
              }
            );
        }
      }
    ]);
    

    APP.js:

      app.post('/api/therapist-login', therapist_controller.login);
    

    Controller:

      module.exports.login = function(req, res) {
    
        var userid = req.body.userid;
        var password = req.body.password;
        console.log(userid + password);
    
        Credentials.findOne({
          'userid': [userid],
          'password': [password]
        }, function(err, user) {
          if (!user) {
            console.log("logged err");
            res.status(404); //Send error response here
            enter code here
          } else {
            console.log("login in");
          }
        });
      }
    

    9 min read

    This article is part of A Guide to Express API Validation.

    When you’re creating an Express API it can be difficult to know how to handle error cases and send consistent error responses. It becomes even more complicated if you want to send helpful error responses with extra details about what went wrong.

    You know these extra details are needed because they’ll also be super helpful for debugging requests to your API, but before you know it, you find yourself designing your own error response format. It all feels awkward, and like it’s probably something you shouldn’t be doing, but what alternative is there?

    Thankfully, there’s an awesome alternative, and you can find it in the ‘Problem Details for HTTP APIs’ specification (RFC7807). Don’t worry though, I don’t expect you to go and read the whole RFC (Request for Comments) document. I know that RFCs aren’t always the easiest of reads, but I think the ideas in this one are so good that I’ve done the RFC reading for you and pulled out all of the good stuff that can help you with formatting your API error responses.

    In this article we’ll explore the Problem Details specification and how it can help you build better APIs. By learning how to apply this well-defined and structured approach, your struggles with creating API error responses will be a thing of the past.

    Jump links

    • Introducing the ‘Problem Details for HTTP APIs’ specification
    • Problem types and Problem details objects
    • Example problem details response
      • More details, clearer problems
      • Breakdown of a problem details object
    • How to send problem details responses with Express
      • Define problem types and map them to JavaScript error classes
      • Look up the problem details for an error
      • Create an error handler to send a problem details response
      • Use the problem details response error handler
      • Example problem details error responses
    • Next steps

    Introducing the ‘Problem Details for HTTP APIs’ specification

    The aim of the problem details specification is to define a common error format which you can use for the error responses from your API. This avoids having to invent your own error response format or, even worse, attempting to redefine the meaning of existing HTTP status codes. Seriously, don’t do this! The meaning of HTTP status codes are well documented and commonly understood for a reason.

    The status codes defined in the HTTP specification are very useful, and often provide enough context to the client as to what went wrong, but they don’t always convey enough information about an error to be helpful.

    Take for example the status code 422 (Unprocessable Entity) – as defined in the HTTP specification, it tells a client that the server understood the request body and its structure, but was unable to process it. However, that alone doesn’t tell the client specifically what was wrong with the JSON that was sent in the request body. Problem details can help you solve this problem.

    The specification describes a problem detail as «a way to carry machine-readable details of errors in a HTTP response». Let’s take a look at how the problem details specification defines them.

    Problem types and Problem details objects

    The problem details specification defines what a «problem type» and a «problem details object» are, and their relationship:

    Problem type – A problem type definition must include a type URI (typically a URL), a short title to describe it and the HTTP status code for it to be used with.

    If required, the definition can also specify additional properties to be included on problem details objects which use this type e.g. balance and accounts in the example above. These additional properties are referred to as «extensions» by the specification.

    The type URI is effectively the namespace for the problem type definition. If the definition changes, the type should also change.

    You should avoid defining a new problem type when the response HTTP status code provides enough context by itself. The specification gives the following example: «a ‘write access disallowed’ problem is probably unnecessary, since a 403 Forbidden status code in response to a PUT request is self-explanatory».

    Problem details object – An object which includes the type, title and status properties for a problem type. This object represents a specific occurrence of that problem type. It can optionally contain a detail property – a human-readable explanation specific to this occurrence of the problem – and an instance property – a URI reference that identifies the specific occurrence of the problem.

    A problem details object should include values for any extensions specified by the problem type definition.

    Problem detail objects can be formatted as XML or JSON. For the purpose of this article we’ll be using JSON formatted problem details.

    Example problem details response

    The response body in this example contains a problem details object of the type https://example.com/probs/out-of-credit:

    HTTP/1.1 403 Forbidden
    Content-Type: application/problem+json
    Content-Language: en
    
    {
    	"type": "https://example.com/probs/out-of-credit",
    	"title": "You do not have enough credit.",
    	"detail": "Your current balance is 30, but that costs 50.",
    	"instance": "/account/12345/msgs/abc",
    	"balance": 30,
    	"accounts": ["/account/12345", "/account/67890"]
    }

    — Source: RFC7807 – Problem Details for HTTP APIs.

    Note how the example response above contains the header Content-Type: application/problem+json. This is the media type for JSON problem details which is defined by the problem details specification. Clients can use the Content-Type header in a response to determine what is contained in the response body. This allows them to handle different types of response bodies in different ways.

    Any response containing a problem details object must also contain the Content-Type: application/problem+json header.

    More details, clearer problems

    Including problem details in the response body allows the client to derive more information about what went wrong, and gives it a better chance of being able to handle the error appropriately. Every problem details object must have a type property. The client can then use the value of the type to determine the specific type of problem which occurred.

    In the example problem details object above (Example 3.1), the problem can be identified as an «out of credit» problem when the client checks the value of the type field: https://example.com/probs/out-of-credit

    The type for a problem can be specific to your API, or you can potentially reuse existing ones if you wish.

    Breakdown of a problem details object

    To better understand the properties which make up a problem details object, let’s break it down and look at each property. Let’s start with our example problem details object:

    {
    	"type": "https://example.com/probs/out-of-credit",
    	"title": "You do not have enough credit.",
    	"detail": "Your current balance is 30, but that costs 50.",
    	"instance": "/account/12345/msgs/abc",
    	"balance": 30,
    	"accounts": ["/account/12345", "/account/67890"]
    }

    Now let’s go through this line by line:

    "type": "https://example.com/probs/out-of-credit",

    The type URI for the problem type being used by this problem details object. The specification encourages that this is a real URL which provides human-readable documentation in HTML format. The client should use the value of this field as the primary identifier for the problem.

    "title": "You do not have enough credit.",

    The title defined by the problem type.

    "status": 403,

    The HTTP status code defined by the problem type. Should be the same as the status code sent in the response from the API.

    As intermediaries between the client and the server (e.g. a proxy or a cache) might modify the response status code, this value can be used by the client to determine the original status code of the response. Also useful in situations where the response body is the only available part of the response e.g. in logs.

    "detail": "Your current balance is 30, but that costs 50.",

    A human-readable explanation of the problem. It should focus on helping the client correct the problem. Machine-readable information should be added in extensions (see below). Specific to this occurrence of the problem.

    "instance": "/account/12345/msgs/abc",

    A URI reference for the specific problem occurrence. Typically a URL, optionally containing more information. Specific to this occurrence of the problem.

    "balance": 30,
    "accounts": ["/account/12345", "/account/67890"]

    Extensions specified by the problem type. Specific to this occurrence of the problem.

    The type, title and status – as defined by a problem type – should be the same for every occurrence of the problem.

    Note: As with any response you send from your API, you should be careful when creating problem details objects that you don’t expose any of the implementation details of your application, as this can make it potentially vulnerable to attack.

    How to send problem details responses with Express

    Now that we’ve covered the concepts and conventions of problem details, we can write some code. This code will allow us to send problem details error responses from our Express API.

    Note: This article uses ECMAScript (ES) module syntax. ES modules are well supported in the v12 and v14 releases of Node.js.

    For Node.js 12.x, I recommend using v12.20.0 or higher. For Node.js 14.x, I recommend using v14.13.0 or higher. These releases of Node.js have stable ES module support, as well as improved compatibility with the older style CommonJS modules, which are still widely used.

    Define problem types and map them to JavaScript error classes

    In this code we’re going to define two different problem types and map them to JavaScript error classes – in this case, ones that are provided by the http-errors library. We’ll use these problem types later on when we create an error handler middleware.

    // src/middleware/problem-details-response.js
    
    import createHttpError from "http-errors";
    
    const defaultProblemDetails = {
    	/**
    	 * This is the only URI reserved as a problem type in the
    	 * problem details spec. It indicates that the problem has
    	 * no additional semantics beyond that of the HTTP status code.
    	 */
    	type: "about:blank",
    	status: 500,
    };
    
    const problemTypes = [
    	{
    		matchErrorClass: createHttpError.BadRequest,
    		details: {
    			type: "https://example-api.com/problem/invalid-user-id",
    			title: "User ID must be a number",
    			status: 400,
    		},
    	},
    	{
    		matchErrorClass: createHttpError.Forbidden,
    		details: {
    			type: "https://example-api.com/problem/user-locked",
    			title: "User has been locked",
    			status: 403,
    		},
    	},
    ];

    Look up the problem details for an error

    Now let’s create a function which, when passed an error object, will look through our array of problemTypes for one which has been mapped to the type of error it has received:

    // src/middleware/problem-details-response.js
    
    /**
     * Get the problem details which have been defined for an error.
     *
     * @param {Error} error
     * @return {Object} - Problem details (type, title, status)
     */
    function getProblemDetailsForError(error) {
    	const problemType = problemTypes.find((problemType) => {
    		/**
    		 * Test if the error object is an instance of the error
    		 * class specified by the problem type.
    		 *
    		 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
    		 */
    		return error instanceof problemType.matchErrorClass;
    	});
    
    	if (!problemType) {
    		/**
    		 * A problem type hasn't been defined for the type of error 
    		 * this function has received so return fallback problem details.
    		 */
    		return defaultProblemDetails;
    	}
    
    	return problemType.details;
    }

    Create an error handler to send a problem details response

    This error handler middleware is going to call the getProblemDetailsByError() function which we just defined, and then send the problem details that it returns as a response body, along with the correct HTTP status code and Content-Type header:

    // src/middleware/problem-details-response.js
    
    /**
     * Send an error response using the problem details format.
     *
     * @see https://tools.ietf.org/html/rfc7807
     *
     * @param {Error} error
     * @param {Object} request - Express request object
     * @param {Object} response - Express response object
     * @param {Function} next - Express callback function
     */
    function problemDetailsResponseMiddleware(
    	error,
    	request,
    	response,
    	next
    ) {
    	/**
    	 * If response headers have already been sent,
    	 * delegate to the default Express error handler.
    	 */
    	if (response.headersSent) {
    		return next(error);
    	}
    
    	const problemDetails = getProblemDetailsForError(error);
    
    	/**
    	 * If the problem details don't contain an HTTP status code,
    	 * let's check the error object for a status code. If the
    	 * error object doesn't have one then we'll fall back to a
    	 * generic 500 (Internal Server Error) status code.
    	 */
    	if (!problemDetails.status) {
    		problemDetails.status = error.statusCode || 500;
    	}
    
    	/**
    	 * Set the correct media type for a response containing a
    	 * JSON formatted problem details object.
    	 *
    	 * @see https://tools.ietf.org/html/rfc7807#section-3
    	 */
    	response.set("Content-Type", "application/problem+json");
    
    	/**
    	 * Set the response status code and a JSON formatted body
    	 * containing the problem details.
    	 */
    	response.status(problemDetails.status).json(problemDetails);
    
    	/**
    	 * Ensure any remaining middleware are run.
    	 */
    	next();
    };
    
    export default problemDetailsResponseMiddleware;

    Use the problem details response error handler

    Our error handling middlware will be run when an error object is passed to a next() Express function. When the next() function is called with with an error object, it automatically stops calling all regular middleware for the current request. It then starts calling any error handler middleware which has been configured.

    It’s time to pull everything together. Here is a complete example Express API application, configured to use our problem details error handler middleware:

    // src/server.js
    
    import express from "express";
    import createHttpError from "http-errors";
    
    import problemDetailsResponseMiddleware from "./middleware/problem-details-response.js";
    
    /**
     * Express configuration and routes
     */
    
    const PORT = 3000;
    const app = express();
    
    /**
     * In a real application this would run a query against a
     * database, but for this example it's returning a `Promise`
     * which randomly either resolves with an example user object
     * or rejects with an error.
     */
    function getUserData() {
    	return new Promise((resolve, reject) => {
    		const randomlyFail = Math.random() < 0.5;
    		if (randomlyFail) {
    			reject(
    				"An error occurred while attempting to run the database query."
    			);
    		} else {
    			resolve({
    				id: 1234,
    				first_name: "Bobo",
    				is_locked: true,
    			});
    		}
    	});
    }
    
    /**
     * This route demonstrates:
     *
     * - Creating an error when the user ID in the URL is not numeric.
     * - Creating an error when the (faked) user object from the database
     *   is locked.
     * - Catching a (randomly faked) database error (see `getUserData()`
     *   function above).
     * - Passing all error objects to the `next()` callback so our problem
     *   details response error handler can take care of them.
     */
    app.get("/user/:user_id", (request, response, next) => {
    	const userIdIsNumeric = !isNaN(request.params.user_id);
    
    	if (!userIdIsNumeric) {
    		const error = new createHttpError.BadRequest();
    
    		return next(error);
    	}
    
    	getUserData()
    		.then((user) => {
    			if (user.is_locked) {
    				const error = new createHttpError.Forbidden();
    
    				return next(error);
    			}
    
    			response.json(user);
    		})
    		.catch(next);
    });
    
    app.use(problemDetailsResponseMiddleware);
    
    app.listen(PORT, () =>
    	console.log(`Example app listening at http://localhost:${PORT}`)
    );

    Example problem details error responses

    Here are the error responses that are produced by the code that we’ve just put together:

    < HTTP/1.1 400 Bad Request
    < Content-Type: application/problem+json; charset=utf-8
    < Content-Length: 106
    
    {
    	"type": "https://example-api.com/problem/invalid-user-id",
    	"title": "User ID must be a number",
    	"status": 400
    }
    < HTTP/1.1 403 Forbidden
    < Content-Type: application/problem+json; charset=utf-8
    < Content-Length: 98
    
    {
    	"type": "https://example-api.com/problem/user-locked",
    	"title": "User has been locked",
    	"status": 403
    }
    < HTTP/1.1 500 Internal Server Error
    < Content-Type: application/problem+json; charset=utf-8
    < Content-Length: 35
    
    {
    	"type": "about:blank",
    	"status": 500
    }

    Just look at those beautiful structured error responses! ✨

    Next steps

    Express API Validation Essentials book cover

    Overwhelmed trying to implement validation in your Express API?

    I’ve written a book that pulls together all of the validation concepts and techniques that I’ve shared through articles here on this blog. It combines them into a complete API validation strategy that you can immediately start applying in your Express applications.

    «Concise and a pleasure to read. This book has clarified concepts for me that I couldn’t get right even after years of consulting the Express documentation.»

    — Carles Andres, Lead Engineer at Tellimer

    The book is packed with practical code examples and includes a handy JSON Schema cheat sheet.

    ➜ Confidently apply validation best practices today

    Понравилась статья? Поделить с друзьями:
  • Express exe ошибка при выключении
  • Explorer ошибка 403
  • Explorer ntdll dll ошибка windows 10
  • Explorer exe пустая ошибка
  • Explorer exe ошибка файловой системы 2147416359