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.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ