В современных веб-приложениях на JavaScript маршруты управляют обработкой входящих HTTP-запросов. Ошибки на этих уровнях могут возникать по разным причинам: некорректные данные, сбои при работе с базой данных, ошибки в логике приложения или внешние факторы. Эффективная обработка ошибок критична для обеспечения стабильности приложения и корректной работы клиентской стороны.
Ключевой момент: маршруты должны быть защищены от любых исключений и обеспечивать понятный ответ клиенту.
try...catchНаиболее простой способ обработки ошибок — обернуть код маршрута в
конструкцию try...catch:
app.get('/users/:id', async (req, res) => {
try {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
res.json(user);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
}
});
Особенности:
try блок позволяет перехватывать синхронные и
асинхронные ошибки (при использовании await).catch блок обеспечивает централизованное логирование и
формирование ответа клиенту.В Express и других фреймворках распространён подход с использованием
middleware для централизованной обработки ошибок. Middleware для ошибок
имеет четыре параметра: (err, req, res, next).
Пример:
app.use((err, req, res, next) => {
console.error(err.stack);
const status = err.status || 500;
res.status(status).json({ error: err.message || 'Неизвестная ошибка' });
});
Преимущества:
Часто возникает необходимость передавать более детализированные ошибки. Для этого создаются собственные классы ошибок:
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = 'NotFoundError';
this.status = 404;
}
}
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
this.status = 400;
}
}
Использование в маршруте:
app.get('/products/:id', async (req, res, next) => {
try {
const product = await getProductById(req.params.id);
if (!product) throw new NotFoundError('Продукт не найден');
res.json(product);
} catch (err) {
next(err);
}
});
Ключевой момент: передача ошибки через
next(err) позволяет middleware централизованно обрабатывать
все виды исключений.
При работе с асинхронными функциями важно корректно обрабатывать
промисы. Ошибка в промисе без await может остаться
необработанной. Решением является использование обёрток:
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/orders/:id', asyncHandler(async (req, res) => {
const order = await getOrderById(req.params.id);
if (!order) throw new NotFoundError('Заказ не найден');
res.json(order);
}));
Преимущество: весь асинхронный код автоматически
передаёт ошибки в middleware, без необходимости дублировать
try...catch в каждом маршруте.
Для крупных приложений важно не только возвращать клиенту корректный ответ, но и фиксировать ошибки для анализа. Варианты:
console.error — базовое логирование.winston или
pino — для аудита и анализа.Совет: всегда включать уникальные идентификаторы ошибок и трассировки стека для упрощения отладки.
Важно, чтобы клиент получал информативный, но безопасный ответ:
ValidationError: Неверные данные.Внутренняя ошибка сервера.res.status(err.status || 500).json({
error: err.publicMessage || 'Произошла ошибка на сервере'
});
Ключевой момент: разграничение внутреннего сообщения
(err.message) и публичного (err.publicMessage)
предотвращает утечку чувствительной информации.
Для крупных проектов маршруты объединяются в роутеры. Каждый роутер может иметь собственное middleware для ошибок:
const userRouter = express.Router();
userRouter.get('/:id', asyncHandler(async (req, res) => {
const user = await getUserById(req.params.id);
if (!user) throw new NotFoundError('Пользователь не найден');
res.json(user);
}));
userRouter.use((err, req, res, next) => {
console.error('[User Router Error]', err);
res.status(err.status || 500).json({ error: err.message });
});
app.use('/users', userRouter);
Преимущество: локальная обработка ошибок позволяет добавлять специфические правила для отдельных модулей без влияния на глобальное поведение приложения.