Express.jsMiddleCoding
Как создать кастомный middleware для обработки ошибок в Express?
Error middleware в Express определяется строго по четырём параметрам (err, req, res, next), регистрируется последним через app.use() и должен вызывать res.json() без повторного вызова next.
Кастомный error middleware в Express
Error middleware в Express отличается от обычного тем, что принимает четыре аргумента: (err, req, res, next). Express определяет его именно по сигнатуре — если убрать хотя бы один параметр, функция перестаёт работать как error handler.
Базовая структура
import { Request, Response, NextFunction } from 'express';
export function errorMiddleware(
err: Error,
req: Request,
res: Response,
next: NextFunction // обязателен, даже если не используется
): void {
console.error(`[${new Date().toISOString()}] ${req.method} ${req.path}`, err);
const status = (err as any).status ?? (err as any).statusCode ?? 500;
const message =
process.env.NODE_ENV === 'production' && status === 500
? 'Internal server error'
: err.message;
res.status(status).json({ error: message });
}
Кастомные классы ошибок
// errors/HttpError.ts
export class HttpError extends Error {
constructor(
public readonly status: number,
message: string,
public readonly code?: string
) {
super(message);
this.name = 'HttpError';
}
}
export class NotFoundError extends HttpError {
constructor(resource = 'Resource') {
super(404, `${resource} not found`, 'NOT_FOUND');
}
}
export class ValidationError extends HttpError {
constructor(
message: string,
public readonly fields?: Record<string, string>
) {
super(422, message, 'VALIDATION_ERROR');
}
}
// routes/users.ts
import { NotFoundError, ValidationError } from '../errors/HttpError';
router.get('/users/:id', asyncHandler(async (req, res) => {
const { id } = req.params;
if (!/^\d+$/.test(id)) throw new ValidationError('id must be a number');
const user = await userService.findById(Number(id));
if (!user) throw new NotFoundError('User');
res.json(user);
}));
Полный error middleware с разными типами ошибок
// middleware/errorMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { HttpError } from '../errors/HttpError';
import { ZodError } from 'zod';
export function errorMiddleware(
err: unknown,
req: Request,
res: Response,
next: NextFunction
): void {
// Zod validation error
if (err instanceof ZodError) {
res.status(422).json({
error: 'Validation failed',
fields: err.errors.map(e => ({ path: e.path.join('.'), message: e.message })),
});
return;
}
// Кастомные HTTP-ошибки
if (err instanceof HttpError) {
res.status(err.status).json({
error: err.message,
code: err.code,
});
return;
}
// pg unique violation
if ((err as any).code === '23505') {
res.status(409).json({ error: 'Resource already exists' });
return;
}
// Неизвестная ошибка
console.error('Unhandled error:', err);
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: (err instanceof Error ? err.message : String(err)),
});
}
Регистрация в app.ts
import express from 'express';
import { userRouter } from './routes/users';
import { errorMiddleware } from './middleware/errorMiddleware';
const app = express();
app.use(express.json());
app.use('/api/users', userRouter);
// 404 handler — ПЕРЕД error middleware, ПОСЛЕ маршрутов
app.use((req, res) => {
res.status(404).json({ error: `Route ${req.method} ${req.path} not found` });
});
// Error middleware — ПОСЛЕДНИМ
app.use(errorMiddleware);
export default app;
Подводные камни
- Сигнатура без 4 параметров: если написать
(err, req, res), Express не распознает это как error middleware и ошибки не будут обрабатываться. - Регистрация не последней: error middleware должен быть после всех маршрутов и обычных middleware, иначе до него просто не дойдёт выполнение.
- Не вызывать next() после res.json(): если уже отправили ответ и потом вызовете
next(err)— получите «Cannot set headers after they are sent»; проверяйтеres.headersSent. - Утечка стека ошибок в production:
err.stackсодержит пути файловой системы и версии зависимостей — никогда не отправляйте его клиенту в боевом окружении. - Тип err = any/unknown: в TypeScript
errможет быть чем угодно (не только Error), поэтому проверяйте черезinstanceofперед доступом к свойствам. - Нет обработки ошибок парсинга JSON: если
express.json()получит невалидный JSON, он вызовет next с объектом ошибки типа SyntaxError со статусом 400 — обработайте его явно в error middleware. - Отсутствие логирования: молчаливое поглощение ошибок без
console.errorили внешнего логгера (Pino, Winston) делает отладку production-проблем почти невозможной.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API Express.js.
- Не объясняет, где в lifecycle находится кастомный error-handling middleware.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить кастомный error-handling middleware на примере кода.
- Называет ключевые API: app.use(errorHandler).
- Использует точные API Express.js, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.