Node express 500 error

Функции промежуточного обработчика для обработки ошибок определяются так же, как и другие функции промежуточной обработки, но с указанием для функции обработки ошибок не трех, а четырех аргументов: (err, req, res, next). Например:

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

Функции промежуточного обработчика для обработки ошибок определяются так же, как и другие функции промежуточной обработки, но с указанием для функции обработки ошибок не трех, а четырех аргументов: (err, req, res, next). Например:


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

Промежуточный обработчик для обработки ошибок должен быть определен последним, после указания всех app.use() и вызовов маршрутов; например:


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

app.use(bodyParser());
app.use(methodOverride());
app.use(function(err, req, res, next) {
  // logic
});

Ответы, поступающие из функции промежуточной обработки, могут иметь любой формат, в зависимости от ваших предпочтений. Например, это может быть страница сообщения об ошибке HTML, простое сообщение или строка JSON.

В целях упорядочения (и для фреймворков более высокого уровня) можно определить несколько функций промежуточной обработки ошибок, точно так же, как это допускается для обычных функций промежуточной обработки. Например, для того чтобы определить обработчик ошибок для запросов, совершаемых с помощью XHR, и для остальных запросов, можно воспользоваться следующими командами:


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

app.use(bodyParser());
app.use(methodOverride());
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);

В данном примере базовый код logErrors может записывать информацию о запросах и ошибках в stderr, например:


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

Кроме того, в данном примере clientErrorHandler определен, как указано ниже; в таком случае ошибка явным образом передается далее следующему обработчику:


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

“Обобщающая” функция errorHandler может быть реализована так:


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

При передаче какого-либо объекта в функцию next() (кроме строки 'route'), Express интерпретирует текущий запрос как ошибку и пропустит все остальные функции маршрутизации и промежуточной обработки, не являющиеся функциями обработки ошибок. Для того чтобы обработать данную ошибку определенным образом, необходимо создать маршрут обработки ошибок, как описано в следующем разделе.

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


app.get('/a_route_behind_paywall',
  function checkIfPaidSubscriber(req, res, next) {
    if(!req.user.hasPaid) {

      // continue handling this request
      next('route');
    }
  }, function getPaidContent(req, res, next) {
    PaidContent.find(function(err, doc) {
      if(err) return next(err);
      res.json(doc);
    });
  });

В данном примере обработчик getPaidContent будет пропущен, но выполнение всех остальных обработчиков в app для /a_route_behind_paywall будет продолжено.

Вызовы next() и next(err) указывают на завершение выполнения текущего обработчика и на его состояние. next(err) пропускает все остальные обработчики в цепочке, кроме заданных для обработки ошибок, как описано выше.

Стандартный обработчик ошибок

В Express предусмотрен встроенный обработчик ошибок, который обрабатывает любые возможные ошибки, встречающиеся в приложении. Этот стандартный обработчик ошибок добавляется в конец стека функций промежуточной обработки.

В случае передачи ошибки в next() без обработки с помощью обработчика ошибок, такая ошибка будет обработана встроенным обработчиком ошибок. Ошибка будет записана на клиенте с помощью трассировки стека. Трассировка стека не включена в рабочую среду.

Для запуска приложения в рабочем режиме необходимо задать для переменной среды NODE_ENV значение production.

При вызове next() с ошибкой после начала записи ответа
(например, если ошибка обнаружена во время включения ответа в поток, направляемый клиенту), стандартный обработчик ошибок Express закрывает соединение и отклоняет запрос.

Поэтому при добавлении нестандартного обработчика ошибок вам потребуется делегирование в стандартные
механизмы обработки ошибок в Express в случае, если заголовки уже были отправлены клиенту:


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

In express status codes can be both get and set with properties and methods in an express response object. There is the res.statusCode property than can be used to find out the current http status code, and the res.status method that can be used to set that code. In addition there is the res.sendStatus method that can be used to just set a status code and end the request without sending any data in the same way as the express end response method. So this will be a post on http status codes in express, getting it, setting it and some status code use examples.

1 — Express status and just getting the current http status code.

To get the current http status code of the request object there is the re.statusCode property. By default the status code of a response is 200 with means everything is okay.

1

2

3

4

5

6

7

8

9

let express = require('express'),

app = express();

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

res.send('status: ' + res.statusCode);

});

app.listen(8080);

Some times there is something that might happen that would call for setting a status code other than that of 200 though. For example if the requested resource does not exist it is typical to send a 404 status code. For this there are methods like res.status, and res.sendStatus.

2 — Express status send and end

If a status code is it be sent but with no data body then there is the res.sendStatus response method that can be used. Another option would be to use the status method combined with the end method, or the send method with no arguments given.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

let express = require('express'),

app = express();

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

res.sendStatus(404);

});

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

res.status(404).end();

});

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

res.status(404).send();

});

app.listen(8080);

3 — An Express Status example that involves a 500 (Server Error) status code

Here I put together a quick example that responds with a 500 Internal Server Error if any kind of error happens when attempting to get a resource, it can also potential default to a 400 error for any request that was not handled by any other additional middleware regardless if there is an error or not.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

let express = require('express'),

fs = require('fs'),

path = require('path'),

app = express();

app.set('port', process.argv[2] || process.env.PORT || 8080);

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

fs.readdir(process.cwd(), (e, files) => {

if (e) {

next(e);

} else {

let html = '';

files.forEach((file) => {

html += '<p><a href="/' + file + '">' + file + '</a></p>'

});

res.set('Content-Type', 'text/html');

res.send(html);

}

});

});

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

fs.readFile(path.join(process.cwd(), req.path), 'utf8', (e, text) => {

if (e) {

next(e);

} else {

res.set('Content-Type', 'text/plain');

res.send(text);

}

});

});

app.use([

(err, req, res, next) => {

if (err) {

res.status(500).send(err.message || 'Internal Server Error');

} else {

next();

}

},

(req, res) => {

res.status(400).send('Bad request');

}

]);

app.listen(app.get('port'), () => console.log('app is up on port: ' + app.get('port')));

The example generates an index of files that are in the current working directory when the root path is visited. All other get requests result in the example attempting to read a file at the path relative to the current working directory. In the event that the file is not there, or an attempt is made to read a directory, an error occurs resulting in a 500 error status being sent.

A 500 status code is very general of course, it just means an internal or server side error has occurred. Same has with a 400 status code as well, it just means Bad Request and it is not more specific like a 404 (not found) or a 403 Forbidden. It is a good idea to stick to codes like 500, or 400 when it comes to making things more general. However in a real project it would be more professional to use more specific codes.

4 — Conclusion

In express status codes can be set with the res.status response method, and the current http status can always be found via the res.statusCode property. Of course there is much more to write about when it comes to the many different status codes, and writing much more complicated express middlewares and client systems. I wanted to at least start a post on this subject though, and hopefully I will get around to expanding on this at some point as well. In the mean time you might want to check out my main post on express for more content on expressjs related topics.

Когда я только начинал работать с 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-проектах?

 on
April 29, 2021

A Guide to Error Handling in Express.js

Error handling often doesn’t get the attention and prioritization it deserves. Especially for newbie developers, there is more focus on setting up routing, route handlers, business logic, optimizing performance, etc. As a result, the equally (if not more) crucial error-handling part will likely be overlooked. Striving for the most optimized code and squeezing out every last ounce of performance is all well and good; yet, it’s important to remember all it takes is one unhandled error leak into your user interface to override all the seconds you helped your users save.

Because there are so many components involved in a successful, functioning web application, it is vital to foolproof your application by preparing for all possible errors and exceptions. If left mishandled, these errors can lead to a bad user experience and end up affecting your business. At the same time, errors provide critical information about potential errors in your application that could bring the whole thing down. Therefore, you must be thoughtful and intelligent about error handling in your application. 

This post will c, Node.js’s most popular server-side framework (even though most of these concepts apply to other frameworks too). Express does a great job taking care of several unhandled errors and provides an easy-to-use, flexible API that developers can utilize to build error handling middleware. 

Here’s an outline of what we’ll be covering so you can easily navigate or skip ahead in the guide:

  • How does Error Handling Work in Express.js?
    • Express Middleware Functions
    • Default Error Handling in Express.js
  • Handling Custom Errors
    • Custom Handling for Each Route
    • Writing your own Error Handling Middleware Functions
    • Adding Multiple Middleware Handlers
  • Basic Quick Tutorial: Setting up Error Handling in Express.js

How Does Error Handling Work in Express.js? 

Express.js is the most popular Javascript server-side framework, perhaps, primarily because of its ease of usage and getting started. One of the many ways it makes things easier is by automatically catching all errors in route handlers, and allowing developers to extend route handling functionalities by leveraging useful middleware functions. 

Before we see how all of this works, let’s briefly visit the concept of middleware functions in Express – most error handling functionality is achieved through these functions. 

Express Middleware Functions 

Middleware functions in Express are essentially functions that come into play after the server receives the request and before the response fires to the client. They have access to the request and the response objects. They can be used for any data processing, database querying, making API calls, sending the response, or calling the next middleware function (using the next() function). 

Two aspects of middleware functions to keep in mind are:

  • They are triggered sequentially (top to bottom) based on their sequence in code.
  • They operate until the process exits, or the response has been sent back to the client.

Let’s understand this through a small example. Below we define two middleware functions using the .use() function and one route handler (skipping the boilerplate code for the sake of simplicity):

app.use((req, res, next) => {
  console.log("Middleware 1 called.")
  console.log(req.path)
  next() // calling next middleware function or handler
})

app.get('/', (req, res) => {
  console.log("Route handler called.")
  res.send("Hello world!") // response sent back – no more middleware called
})

app.use((req, res, next) => {
  console.log("Last middleware called❓") // not called
})

Here, each time the server receives a request, the first middleware is fired, followed by the corresponding route handler (using the next() function). However, because the response returns in this handler, the last middleware function is not called. Here’s the output:

undefined
Server output

Several native as well as third-party middleware functions have been made available by the Express community and are widely for adding functionalities like session management, authentication, logging, redirecting, and so much more. This was a basic example of how middleware functions work. We will come back to them when discussing how to utilize them for error handling in our applications.

Default Error Handling in Express.js 

Express implicitly takes care of catching your errors to prevent your application from crashing when it comes to error handling. This is especially true for synchronous route handler code. Let’s see how:

Synchronous Code

Synchronous code refers to statements of code that execute sequentially and one at a time. When an error encounters synchronous code, Express catches it automatically. Here’s an example of a route handler function where we simulate an error condition by throwing an error:

app.get('/', (req, res) => {
  throw new Error("Hello error!")
})

Express catches this error for us and responds to the client with the error’s status code, message, and even the stack trace (for non-production environments).

All of this is taken care of thanks to Express’s default built-in error handler middleware function inserted at the end of your code’s middleware stack. This automatic handling saves you from bulky try/catch blocks and explicit calls to the in-built middleware (shown below) while also providing some fundamental default error handling functionality. 

app.get('/', (req, res, next) => {
  try {
      throw new Error("Hello error!")
  }
  catch (error) {
      next(error)
  }
})

You can also choose to create your own middleware function to specify your error handling logic. 

Asynchronous Code

When writing server-side code, most of your route handlers are likely using asynchronous Javascript logic to read and write files on the server, query databases, and make external API requests. Let’s see whether Express can catch errors raised from asynchronous code as well. We’ll throw an error from inside the asynchronous setTimeout() function and see what happens:

app.get('/', (req, res) => {
  setTimeout(() => {
      console.log("Async code example.")
      throw new Error("Hello Error!")
  }, 1000)
})

As you can see, our server crashed because Express didn’t handle the error for us. 

undefined
Server output

For handling errors raised during asynchronous code execution in Express (versions < 5.x), developers need to themselves catch their errors and invoke the in-built error handler middleware using the next() function. Here’s how:

app.get('/', (req, res, next) => {
  setTimeout(() => {
      try {
          console.log("Async code example.")
          throw new Error("Hello Error!")
      } catch (error) { // manually catching
          next(error) // passing to default middleware error handler
      }
  }, 1000)
})
undefined
Browser output

This is much better – we caught the error, and our server didn’t crash. This does look a little bulky because we used the setTimeout() function to demonstrate async behavior. This function does not return a promise and, therefore, can’t be chained with a quick .catch() function. However, most libraries that help with async operations return promises these days (e.g., the file system API). Below is an example of a more convenient and common way of catching errors from promises:

const fsPromises = require('fs').promises
app.get('/', (req, res, next) => {
  fsPromises.readFile('./no-such-file.txt')

     .then(data => res.send(data))

     .catch(err => next(err)) 
})

Note: Express 5.0 (currently in alpha) can automatically catch errors (and rejections) thrown by returned Promises. 

Handling Custom Errors 

Express’s default error-handling middleware is super helpful for beginners to take care of unexpected, unhandled errors. However, different developers and organizations would want their errors handled in their own way – some might want to write these to log files, others might want to alert the user or redirect them to another page, or all of the above.

Custom Handling for Each Route 

An obvious, naive way of going about this would be to define your custom error handling logic for each route handler as so:

const express = require('express')
const fsPromises = require('fs').promises;

const app = express()
const port = 3000

app.get('/one', (req, res) => {
  fsPromises.readFile('./one.txt')
    .then(data => res.send(data))
    .catch(err => { // error handling logic 1
        console.error(err) // logging error
        res.status(500).send(err)
    })
})

app.get('/two', (req, res) => {
  fsPromises.readFile('./two.txt')
    .then(data => res.send(data))
    .catch(err => { // error handling logic 2
        console.error(err)
        res.redirect('/error') // redirecting user
    })
})

app.get('/error', (req, res) => {
  res.send("Custom error landing page.")
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Here, we specified two different handling logics – one for each route that attempts to read arbitrary files on the server. As you can imagine, this would get too redundant quickly and wouldn’t scale well as you add more and more routes.

Writing your Error Handling Middleware Functions 

A much better option would be to leverage Express’s middleware functions here. You could write one or more middleware functions for handling errors in your application that all of your routes could utilize by making simple next() calls. 

Middleware functions are much more convenient to work with than conventional functions because they automatically have access to the error, request, and response objects and can be invoked (or invoke others) based on their ordering using just the next() function.

You can create your own error handling middleware functions by adding the error argument to the function, apart from request, response, and next. Here is an example:

app.use((error, req, res, next) => {
  console.log("Error Handling Middleware called")
  console.log('Path: ', req.path)
  next() // (optional) invoking next middleware
})

Another thing to keep in mind is the ordering of the middleware. The error handler needs to specify middleware functions after the route handlers for the next(error) calls to be directed towards them.

Now let’s recreate the previous example, but this time with an error-handling middleware in place.

const express = require('express')
const fsPromises = require('fs').promises

const app = express()
const port = 3000

app.get('/one', (req, res, next) => {
  fsPromises.readFile('./one.txt') // arbitrary file
    .then(data => res.send(data))
    .catch(err => next(err)) // passing error to custom middleware
})

app.get('/two', (req, res, next) => {
  fsPromises.readFile('./two.txt')
    .then(data => res.send(data))
    .catch(err => {
        err.type = 'redirect' // custom prop to specify handling behaviour
        next(err)
    })
})

app.get('/error', (req, res) => {
  res.send("Custom error landing page.")
})

app.use((error, req, res, next) => {
  console.log("Error Handling Middleware called")
  console.log('Path: ', req.path)
  console.error('Error: ', error)
 
  if (error.type == 'redirect')
      res.redirect('/error')

   else if (error.type == 'time-out') // arbitrary condition check
      res.status(408).send(error)
  else
      res.status(500).send(error)
})


app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

Instead of defining the handling behavior inside each route, we place all our logic inside the middleware. Then, based on the kind of error, we can modify the error object (or throw a custom error) and accordingly deal with it in the middleware.

This allows us to achieve the same functionality as before, but more efficiently. Assuming these files are not present on the server, if we go to /one, the server logs the error and sends back a 500 (internal server error) response. We are redirected to the /error page after the error logs if we open /two. Below are the corresponding client and server outputs:

As you can imagine, this was a fairly basic example just to give you a sense of how you can decouple your error handling logic from the route handling into a middleware function. This extends to larger applications with hundreds of routes for increased modularity, reduced redundancy, easier maintenance, and more efficient exception handling.

Adding Multiple Middleware Handlers 

In the previous section, we worked with just one middleware to handle all our errors. However, in practice, multiple middleware functions are usually employed for different aspects of error handling to have further abstractions. For example, one middleware for logging errors, another for responding to the client, perhaps another as a fail-safe catch-all handler, etc. Here’s a preview of the same based on our previous example:

// route handlers
app.get('/one')
app.get('/two') 

app.get('/error')
// middleware

app.use(errorLogger)
app.use(errorResponder)
app.use(failSafeHandler)

Let’s write the code for this.

const express = require('express')
const fsPromises = require('fs').promises

const app = express()
const port = 3000

app.get('/one', (req, res, next) => {
  fsPromises.readFile('./one.txt')
  .then(data => res.send(data))
  .catch(err => next(err)) // passing error to custom middleware
})

app.get('/two', (req, res, next) => {
  fsPromises.readFile('./two.txt')
  .then(data => res.send(data))
  .catch(err => {
      err.type = 'redirect' // adding custom property to specify handling behaviour
      next(err)
  })
})

app.get('/error', (req, res) => {
  res.send("Custom error landing page.")
})

function errorLogger(error, req, res, next) { // for logging errors
  console.error(error) // or using any fancy logging library
  next(error) // forward to next middleware
}

function errorResponder(error, req, res, next) { // responding to client
  if (error.type == 'redirect')
      res.redirect('/error')
  else if (error.type == 'time-out') // arbitrary condition check
      res.status(408).send(error)
  else
      next(error) // forwarding exceptional case to fail-safe middleware
}

function failSafeHandler(error, req, res, next) { // generic handler
  res.status(500).send(error)
}

app.use(errorLogger)
app.use(errorResponder)
app.use(failSafeHandler)

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

This allows us to achieve the same functionality as in the previous code example, but in a more modular way that would scale better as you add more routes and handle more error conditions.

However, as previously discussed, when working with multiple middleware functions, one must keep an eye on their sequence and remember that each middleware should either respond to the client or invoke the subsequent one in the stack. If the server is just left to hang, the client continues to wait. For example, if we missed using next() in the first middleware (errorLogger), the subsequent middleware functions are not invoked, and therefore, no response fires. 

Basic Quick Tutorial: Setting up Error Handling in Express.js

Now that we’ve covered almost all aspects of error handling in Express, theory-wise, let’s solidify our understanding of these concepts by creating a prototype Express application that handles errors using middleware methods in a relatively more realistic setting.

We’ll create an API that serves user posts data fetched from a dummy API (jsonplaceholder.typicode.com). We will then validate some of the posts’ properties based on some arbitrary criteria (e.g., the content length), raise custom errors if validation fails, capture these using our custom middleware, and process them accordingly. 

Step 1: Create and Setup Project 

First, create an empty folder, cd into it, generate an npm project, and install the dependencies.

mkdir my-express-app && cd my-express-app
npm init -y
npm i --save express node-fetch

Then, create files – index.js, routes.js, errors.js, and middleware.js. It is considered good practice to keep your routes, main file, and other utilities in separate files. Ideally, developers prefer different folders for better organization, but for our small prototype, just files would suffice.

undefined
Project files

Step 2: Setup the Server 

Now let’s write the code that will start our server in index.js.

// index.js
const express = require('express')
const app = express()
const port = 3000

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

We’ll start the server and make sure everything is working fine by running the node index.js command from inside the folder.

undefined
Console output

Step 3: Create Some Routes 

Now let’s create some routes in the routes.js file, and for now, just fetch some dummy JSON posts data from the dummy API (jsonplaceholder.typicode.com/posts), and serve it through our route. We will use Express’s Router module and export our routes – to import into our main index.js server file.

// routes.js
const express = require('express')
const fetch = require('node-fetch') // for making external API requests
const router = express.Router()
router.get('/', (req, res) => {
    res.send("Hello World!")
})

router.get('/user-posts', (req, res, next) => {
  fetch('https://jsonplaceholder.typicode.com/posts')
      .then(res => res.json())
      .then(data => {
          console.log(data)
          res.header("Content-Type",'application/json');
          res.send(JSON.stringify(data, null, 4)) // pretty print
        })
      .catch(err => next(err)) // pass to default error handler middleware
})

router.get('/error', (req, res) => {
  res.send("The URL you are trying to reach does not exist.")
})

module.exports = router // export routes

Now let’s import these routes into our server file.

// index.js
const express = require('express')
const routes = require('./routes') // importing routes

const app = express()
const port = 3000

app.use(routes) // initializing routes

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

Let’s fire up our browser and check whether the route is working.

undefined
Browser output

Our server seems to be working fine here. Let’s do some error handling now.

Step 4: Creating and Handling Custom Errors 

It is pretty helpful to create custom error classes for your applications by extending Node’s Error class in practice. These errors can raise issues specific to the application, for example – unauthorized access, unsuccessful payment, incorrect user input, etc. This allows developers to have more detailed information about the error conditions (through custom error messages and other properties), and therefore handle them better.

In our use case, let’s say we want to ensure that all the posts have a title of fewer than 100 characters and a body character count of fewer than 220 characters. If we don’t meet this condition, we want to raise a custom error message that alerts the developer about the same time. 

Now that we have the error condition in mind, let’s create our custom error classes in the errors.js file.

// errors.js
class CharacterCountExceeded extends Error { // parent error
  constructor(post_id, content) {
      super();
      this.name = this.constructor.name // good practice

      if (this instanceof LongTitleError) // checking if title or body
          this.type = 'title'
      else if (this instanceof LongBodyError)
          this.type = 'body'
 
    this.message = `The character count of post (id: ${post_id}) ${this.type} is too long. (${content.length} characters)` // detailed error message
    this.statusCode = 500 // error code for responding to client
  }
}

// extending to child error classes
class LongTitleError extends CharacterCountExceeded { }
class LongBodyError extends CharacterCountExceeded { }

module.exports = {
    CharacterCountExceeded,
    LongTitleError,
    LongBodyError
}

First, we create one parent error class (CharacterCountExceeded) for all errors that involve an exceeded character count. The constructor for this class accepts the post’s ID and the content (of the title or body) to generate the required error message and specify an error code. Then we extend this class to create two more specific children classes (LongTitleError and LongBodyError) that refer to the particular error condition.

Now we will import these into our routes.js file, check for erroneous conditions inside our route handler, and throw these custom errors wherever required.

// routes.js
const express = require('express')
const fetch = require('node-fetch')
const router = express.Router()
const { LongTitleError, LongBodyError } = require('./errors');
router.get('/', (req, res) => {
  res.send("Hello World!")
})

router.get('/user-posts', (req, res, next) => {
  fetch('https://jsonplaceholder.typicode.com/posts')
      .then(res => res.json())
      .then(posts => {
          for (post of posts) {
              if (post.title.length > 100)
                  throw new LongTitleError(post.id, post.body)
              if (post.body.length > 220)
                  throw new LongBodyError(post.id, post.body) 
          }
          console.log(posts)
          res.header("Content-Type", 'application/json')
          res.send(JSON.stringify(posts, null, 4)) // pretty print
      })
      .catch(err => next(err))
})

router.get('/error', (req, res) => {
    res.send("The URL you are trying to reach does not exist.")
})

module.exports = router

As you can see here, we traverse through all the posts, check for their title and body’s character count, and throw our custom errors accordingly. Here’s the output:

undefined

It turns out there was one post that had a body size of more than 220 characters, and we successfully captured it. At the moment, we are forwarding all our errors through the catch block to Express’s default error handler middleware. But what’s the fun in that?

Let’s create our own middleware functions and use them as we like.

Step 5: Adding Custom Error Handler Middleware

We’ll use the middleware.js file that we created before.

// middleware.js
const errorLogger = (err, req, res, next) => {
  console.error('x1b[31m', err) // adding some color to our logs
  next(err) // calling next middleware
}

const errorResponder = (err, req, res, next) => {
  res.header("Content-Type", 'application/json')
  res.status(err.statusCode).send(JSON.stringify(err, null, 4)) // pretty print
}
const invalidPathHandler = (req, res, next) => {
  res.redirect('/error')
}

module.exports = { errorLogger, errorResponder, invalidPathHandler }

Here, we add three middleware functions – one for logging errors, one for sending the error to the client, and one for redirecting a user from an invalid route to an error landing page. Now let’s import these into our main file and use them in our application.

// index.js
const express = require('express')
const routes = require('./routes')
const { errorLogger, errorResponder, invalidPathHandler } = require('./middleware')

const app = express()
const port = 3000

app.use(routes)

// middleware
app.use(errorLogger)
app.use(errorResponder)
app.use(invalidPathHandler)

app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

Now let’s open our browser and see whether the middleware handles our LongBody error the way it’s supposed to.

undefined
Console output: Error object logged in red using the errorLogger middleware.
undefined
Server’s error response using the errorResponder middleware.
undefined
Redirecting to the error landing page upon encountering an invalid path

As you can see all our middleware functions are working as expected – in logging, responding to the client, and redirecting to the error landing page.

Wrapping it Up

We covered everything about error handling in Express.js – from default error handling of synchronous and asynchronous code to creating your own error classes and writing your own error-handling middleware functions. 

Now go ahead and make sure to handle all your errors in your Express application in a clean, non-redundant, efficient, and easy to maintain way. And if you haven’t already, write your own middleware functions and play around with native and third-party ones to explore how they can be helpful for your applications. If you are serious about your application’s performance and want to spend less time debugging issues and more time building new features, consider checking out ScoutAPM for monitoring your Node.js app’s performance and get started with a 14-day free trial.

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

Функции промежуточного обработчика для обработки ошибок определяются так же, как и другие функции промежуточной обработки, но с указанием для функции обработки ошибок не трех, а четырех аргументов: (err, req, res, next). Например:

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

Промежуточный обработчик для обработки ошибок должен быть определен последним, после указания всех app.use() и вызовов маршрутов; например:

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

app.use(bodyParser())
app.use(methodOverride())
app.use(function (err, req, res, next) {
  // logic
})

Ответы, поступающие из функции промежуточной обработки, могут иметь любой формат, в зависимости от ваших предпочтений. Например, это может быть страница сообщения об ошибке HTML, простое сообщение или строка JSON.

В целях упорядочения (и для фреймворков более высокого уровня) можно определить несколько функций промежуточной обработки ошибок, точно так же, как это допускается для обычных функций промежуточной обработки. Например, для того чтобы определить обработчик ошибок для запросов, совершаемых с помощью XHR, и для остальных запросов, можно воспользоваться следующими командами:

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

app.use(bodyParser())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

В данном примере базовый код logErrors может записывать информацию о запросах и ошибках в stderr, например:

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

Кроме того, в данном примере clientErrorHandler определен, как указано ниже; в таком случае ошибка явным образом передается далее следующему обработчику:

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

«Обобщающая» функция errorHandler может быть реализована так:

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

При передаче какого-либо объекта в функцию next() (кроме строки 'route'), Express интерпретирует текущий запрос как ошибку и пропустит все остальные функции маршрутизации и промежуточной обработки, не являющиеся функциями обработки ошибок. Для того чтобы обработать данную ошибку определенным образом, необходимо создать маршрут обработки ошибок, как описано в следующем разделе.

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

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

В данном примере обработчик getPaidContent будет пропущен, но выполнение всех остальных обработчиков в app для /a_route_behind_paywall будет продолжено.

Вызовы next() и next(err) указывают на завершение выполнения текущего обработчика и на его состояние. next(err) пропускает все остальные обработчики в цепочке, кроме заданных для обработки ошибок, как описано выше.

Стандартный обработчик ошибок¶

В Express предусмотрен встроенный обработчик ошибок, который обрабатывает любые возможные ошибки, встречающиеся в приложении. Этот стандартный обработчик ошибок добавляется в конец стека функций промежуточной обработки.

В случае передачи ошибки в next() без обработки с помощью обработчика ошибок, такая ошибка будет обработана встроенным обработчиком ошибок. Ошибка будет записана на клиенте с помощью трассировки стека. Трассировка стека не включена в рабочую среду.

Для запуска приложения в рабочем режиме необходимо задать для переменной среды NODE_ENV значение production.

При вызове next() с ошибкой после начала записи ответа (например, если ошибка обнаружена во время включения ответа в поток, направляемый клиенту), стандартный обработчик ошибок Express закрывает соединение и отклоняет запрос.

Поэтому при добавлении нестандартного обработчика ошибок вам потребуется делегирование в стандартные
механизмы обработки ошибок в Express в случае, если заголовки уже были отправлены клиенту:

function errorHandler(err, req, res, next) {
  if (res.headersSent) {
    return next(err)
  }
  res.status(500)
  res.render('error', { error: err })
}
  • Remove From My Forums
  • Question

  • I am getting the following error when I visit mydomain.com,
    however the site loads correctly when I visit mydomain.com/ how
    can I fix the issue?

    iisnode encountered an error when processing the request.

    HRESULT: 0x6d
    HTTP status: 500
    HTTP reason: Internal Server Error
    You are receiving this HTTP 200 response because system.webServer/iisnode/@devErrorsEnabled configuration setting is ‘true’.

    In addition to the log of stdout and stderr of the node.exe process, consider using debugging and ETW traces to further diagnose the problem.

    The node.exe process has not written any information to stderr or iisnode was unable to capture this information. Frequent reason is that the iisnode module is unable to create a log file to capture stdout and stderr output from node.exe. Please check that
    the identity of the IIS application pool running the node.js application has read and write access permissions to the directory on the server where the node.js application is located. Alternatively you can disable logging by setting system.webServer/iisnode/@loggingEnabled
    element of web.config to ‘false’.

    app.js

    /**
     * Module dependencies.
     */
    
    var express = require('express');
    var routes = require('./routes');
    var user = require('./routes/user');
    var http = require('http');
    var path = require('path');
    var mongoose = require('mongoose');
    var emailer = require('./models/emailer.js');
    
    var app = express();
    mongoose.connect('mongodb://heroku:awesome@troup.mongohq.com:10038/app22548277');
    var db = mongoose.connection;
    
    // all environments
    app.set('port', process.env.PORT || 3000);
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'jade');
    app.use(express.favicon());
    app.use(express.logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded());
    app.use(express.methodOverride());
    app.use(app.router);
    app.use(express.static(path.join(__dirname, 'public')));
    
    // development only
    if ('development' == app.get('env')) {
      app.use(express.errorHandler());
    }
    
    app.get('/', routes.index);
    app.get('/locations', routes.locations);
    app.get('/locationdetail/:id', routes.locationdetail);
    app.get('/charity', routes.charity);
    app.get('/washpackages', routes.washpackages);
    app.get('/lubecenters', routes.lubecenters);
    app.get('/contact', routes.contact);

    /routes/index.js (the relevant portion, anyway)

    exports.index = function(req, res){
      Testimonial.find(function(err, results) {
        res.render('index', { title: 'Home', testimonials: results });
      });
    };

    Any ideas?

Answers

  • hi,

    >>I am getting the following error when I visit mydomain.com,
    however the site loads correctly when I visit mydomain.com/ how
    can I fix the issue?

    From your description, you could need check your domain name setting from your domain registrar . I guess you may input additional slash in your domain name setting.

    >>In addition to the log of stdout and stderr of the node.exe process, consider using debugging and ETW traces to further diagnose the problem.

    If you host your website on azure website, I suggest you could enable the Site Diagnostics on azure website configure panel. You could see your log file from FTP Diagnostic Logs. 

    Also, you could see this link:

    http://azure.microsoft.com/en-us/documentation/articles/web-sites-enable-diagnostic-log/

    Regards,

    Will


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.

    Click
    HERE to participate the survey.

    • Marked as answer by

      Monday, April 21, 2014 1:12 AM

Понравилась статья? Поделить с друзьями:
  • No space left on device как исправить
  • No space between attributes как исправить
  • No sound device detected skyrim cannot run как исправить
  • Nod32 при установке драйвера edevmon произошла ошибка
  • Nod32 ошибка обновления 0x4002