Node.jsМодуль 4: HTTP и Express
Обработка ошибок
Централизованная обработка ошибок в Express
Цель урока
В этом уроке ты научишься:
- Обрабатывать ошибки централизованно
- Создавать кастомные классы ошибок
- Логировать ошибки
Error Middleware
Middleware с 4 параметрами обрабатывает ошибки:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong' });
});Важно
Error middleware должен быть объявлен последним, после всех маршрутов.
Передача ошибок
Синхронный код
app.get('/user/:id', (req, res, next) => {
const id = parseInt(req.params.id);
if (isNaN(id)) {
// Передаём ошибку в error middleware
return next(new Error('Invalid ID'));
}
res.json({ id });
});Асинхронный код
app.get('/users', async (req, res, next) => {
try {
const users = await User.find();
res.json(users);
} catch (error) {
next(error);
}
});
// Или с обёрткой
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/users', asyncHandler(async (req, res) => {
const users = await User.find();
res.json(users);
}));Кастомные ошибки
Базовый класс
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = AppError;Специфичные ошибки
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
}
}
class ValidationError extends AppError {
constructor(message = 'Validation failed') {
super(message, 400);
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401);
}
}
class ForbiddenError extends AppError {
constructor(message = 'Forbidden') {
super(message, 403);
}
}Использование
const { NotFoundError, ValidationError } = require('./errors');
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
res.json(user);
}));
app.post('/users', asyncHandler(async (req, res) => {
const { email } = req.body;
if (!email) {
throw new ValidationError('Email is required');
}
const user = await User.create(req.body);
res.status(201).json(user);
}));Централизованный обработчик
const errorHandler = (err, req, res, next) => {
// Логирование
console.error('Error:', {
message: err.message,
stack: err.stack,
path: req.path,
method: req.method
});
// Операционные ошибки (ожидаемые)
if (err.isOperational) {
return res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}
// Ошибки валидации Mongoose
if (err.name === 'ValidationError') {
const messages = Object.values(err.errors).map(e => e.message);
return res.status(400).json({
status: 'fail',
message: messages.join(', ')
});
}
// Ошибка дублирования (MongoDB)
if (err.code === 11000) {
return res.status(400).json({
status: 'fail',
message: 'Duplicate field value'
});
}
// JWT ошибки
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({
status: 'fail',
message: 'Invalid token'
});
}
// Неожиданные ошибки
res.status(500).json({
status: 'error',
message: process.env.NODE_ENV === 'production'
? 'Something went wrong'
: err.message
});
};
// Подключение
app.use(errorHandler);404 Handler
// После всех маршрутов, перед error handler
app.use((req, res, next) => {
next(new NotFoundError(`Route ${req.originalUrl} not found`));
});
app.use(errorHandler);Необработанные ошибки
// Необработанные промисы
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
// Graceful shutdown
server.close(() => {
process.exit(1);
});
});
// Необработанные исключения
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
});Полная структура
// app.js
const express = require('express');
const { NotFoundError } = require('./errors');
const errorHandler = require('./middleware/errorHandler');
const app = express();
// Middleware
app.use(express.json());
// Routes
app.use('/api/users', require('./routes/users'));
app.use('/api/posts', require('./routes/posts'));
// 404 handler
app.use((req, res, next) => {
next(new NotFoundError());
});
// Error handler
app.use(errorHandler);
module.exports = app;Практика
Задание 1: Кастомная ошибка
Задача: Создай ошибку ConflictError для дублирования данных.
Решение:
class ConflictError extends AppError {
constructor(message = 'Resource already exists') {
super(message, 409);
}
}
// Использование
app.post('/users', asyncHandler(async (req, res) => {
const existing = await User.findOne({ email: req.body.email });
if (existing) {
throw new ConflictError('Email already registered');
}
const user = await User.create(req.body);
res.status(201).json(user);
}));Проверь себя
- Сколько параметров у error middleware?
- Как передать ошибку из async функции?
- Что такое операционная ошибка?