JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Обработка ошибок: error handler middleware в Express.js

Обработка ошибок: error handler middleware в Express.js

Модуль 4: Node.js, Next.js и Angular
6 уровень , 3 лекция
Открыта

1. Введение

Если вы уже писали на JavaScript, то знаете, что ошибки бывают двух типов: ожидаемые (например, пользователь ввёл не то, что нужно) и неожиданные (например, упал сервер базы данных или кто-то забыл поставить точку с запятой — классика жанра). В Express.js, если ошибка не поймана, сервер может либо зависнуть, либо отправить пользователю неинформативную простыню текста, либо вообще ничего не отправить.

Чтобы избежать хаоса и не превращать код в "лапшу" из try/catch, Express предлагает использовать отдельный middleware для обработки ошибок. Такой подход делает код чище, а приложение — надёжнее и удобнее для поддержки.

Как Express.js понимает, что произошла ошибка

В Express.js любой middleware или обработчик маршрута может "сигнализировать" об ошибке — для этого достаточно вызвать функцию next с аргументом-ошибкой:

app.get('/fail', (req, res, next) => {
  // Ой, что-то пошло не так!
  next(new Error('Что-то пошло не так!'));
});

Когда Express видит, что в next() передан аргумент, он пропускает все обычные middleware и ищет специальный обработчик ошибок — error handler middleware.

Сигнатура error handler middleware

Главное отличие error handler middleware — у него четыре аргумента, а не три:

function errorHandler(err, req, res, next) {
  // обработка ошибки
}
  • err — объект ошибки (Error, строка, что угодно).
  • req — объект запроса.
  • res — объект ответа.
  • next — функция для передачи управления следующему обработчику (чаще всего не используется, но может пригодиться).

Express определяет обработчик ошибок именно по количеству аргументов: если их четыре — это error handler, если три — обычный middleware.

2. Простой пример: базовый error handler

Давайте добавим в наше учебное приложение обработчик ошибок:

// Пример обычного маршрута
app.get('/divide', (req, res, next) => {
  const { a, b } = req.query;
  if (!b || Number(b) === 0) {
    // Передаем ошибку в error handler
    return next(new Error('Деление на ноль невозможно!'));
  }
  res.send(`Результат: ${Number(a) / Number(b)}`);
});

// Error handler middleware — обязательно в самом конце!
app.use(function (err, req, res, next) {
  console.error('Ошибка:', err.message); // Логируем ошибку на сервере
  res.status(500).json({ error: err.message });
});

Теперь если пользователь попробует поделить на ноль, он получит красивый JSON-ответ с ошибкой, а сервер не упадёт.

Где и как подключать error handler

Важно: error handler middleware должен быть последним в цепочке middleware. Express идёт сверху вниз по коду, и если error handler окажется до обычных middleware, он просто не будет вызван.

Правильный порядок:

// Обычные middleware
app.use(express.json());
app.use(myLogger);

// Ваши маршруты
app.get('/route', ...);

// Error handler — в самом конце!
app.use(errorHandler);

3. Передача ошибок: next(err), throw и асинхронные функции

Синхронные ошибки

Внутри обычного маршрута или middleware можно использовать либо next(new Error(...)), либо просто выбросить ошибку через throw. Express сам поймает ошибку и передаст её в error handler.

app.get('/fail', (req, res) => {
  throw new Error('Бум!');
});

Асинхронные ошибки

Тут есть подвох! Если вы используете асинхронные функции (например, промисы или async/await), просто выбросить ошибку недостаточно — Express не сможет её поймать без вашей помощи. Нужно явно передать ошибку в next():

app.get('/async-fail', async (req, res, next) => {
  try {
    // что-то асинхронное
    await doSomething();
    res.send('Всё ок!');
  } catch (err) {
    next(err); // обязательно!
  }
});

Лайфхак: Для Express 5 (на момент написания лекции в бете) поддерживается автоматическая обработка ошибок в async-функциях. В Express 4 — всегда используйте try/catch!

4. Кастомизация error handler: статус, формат, логика

В реальных приложениях error handler обычно делает больше, чем просто отправляет текст ошибки. Вот что можно добавить:

  • Логирование ошибок (например, в файл или систему мониторинга).
  • Разные коды статуса: 400 для ошибок клиента, 404 — не найдено, 500 — ошибка сервера.
  • Скрытие деталей ошибок от пользователя (чтобы не палить внутренности сервера).
  • Отправка ошибок в формате JSON или HTML, в зависимости от типа запроса.

Пример кастомного error handler:

app.use(function (err, req, res, next) {
  // По умолчанию — 500 (Internal Server Error)
  let status = err.status || 500;

  // Можно задать статус при создании ошибки
  // const err = new Error('Не найдено'); err.status = 404;

  // Логируем ошибку (но не показываем stack пользователю)
  console.error(err.stack);

  // Для API — отправляем JSON, для браузера — HTML
  if (req.headers.accept && req.headers.accept.includes('application/json')) {
    res.status(status).json({ error: err.message });
  } else {
    res.status(status).send(`<h1>Ошибка: ${err.message}</h1>`);
  }
});

5. Практика: добавляем error handler в наше приложение

Допустим, у нас есть простое приложение задач (to-do), где задачи хранятся в массиве:

const express = require('express');
const app = express();

app.use(express.json());

let todos = [{ id: 1, text: 'Купить хлеб' }];

// Получить задачу по id
app.get('/todos/:id', (req, res, next) => {
  const id = Number(req.params.id);
  const todo = todos.find(t => t.id === id);
  if (!todo) {
    // Создаем ошибку с кастомным статусом
    const err = new Error('Задача не найдена');
    err.status = 404;
    return next(err);
  }
  res.json(todo);
});

// Error handler — в самом конце!
app.use((err, req, res, next) => {
  const status = err.status || 500;
  console.error(`[${new Date().toISOString()}] Ошибка: ${err.message}`);
  res.status(status).json({ error: err.message });
});

app.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

Теперь если пользователь попробует получить задачу, которой нет, он получит красивый ответ:

{
  "error": "Задача не найдена"
}

6. Типичные ошибки при работе с error handler middleware

Ошибка №1: забыли поставить error handler в конец цепочки.
Если error handler подключён до маршрутов или других middleware, Express может его просто не вызвать. Всегда ставьте его последним.

Ошибка №2: забыли четыре аргумента.
Если у middleware только три аргумента (err, req, res), Express не считает его обработчиком ошибок. Всегда пишите err, req, res, next.

Ошибка №3: не передали ошибку из async-функции.
Внутри async/await-обработчиков обязательно используйте try/catch и передавайте ошибку через next(err), иначе Express не узнает о проблеме.

Ошибка №4: показываем пользователю stack trace.
В продакшене никогда не отправляйте пользователю полные детали ошибки (stack trace) — это может быть опасно (security risk) и просто некрасиво.

Ошибка №5: не задаём статус-код для ошибок.
Если не указать статус, Express отправит 500. Для ошибок типа "не найдено" или "неверный запрос" используйте 404 или 400.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 6 уровень, 3 лекция
Недоступна
Передача ошибки в error handler
Передача ошибки в error handler
1
Задача
Модуль 4: Node.js, Next.js и Angular, 6 уровень, 3 лекция
Недоступна
Кастомизация статуса ошибки и разный формат ответа
Кастомизация статуса ошибки и разный формат ответа
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ