Знаете это чувство, когда открываешь контроллер в Express проекте, чтобы поправить одну строчку логики, и видишь ЭТО? Бесконечная вложенность, проверки на существование полей, ручной парсинг ошибок от базы данных и, конечно же, его величество try-catch, который занимает 80% файла.
Я тоже через это проходил. В каждом новом микросервисе я копипастил одни и те же функции обработки ошибок. В одном проекте я ловил ошибки Mongoose через err.name === 'ValidationError', в другом — через instanceof. Где-то мы отдавали { error: "message" }, где-то { status: "fail", msg: "..." }.
В какой-то момент мне это надоело. Мне захотелось инструмент, который я могу просто подключить одной строкой, и он сам поймет, что "E11000" от Mongo — это 409 Conflict, а ошибка Zod — это 400 Bad Request. При этом я не хотел тянуть в проект тяжелые зависимости.
Так родилась библиотека ds-express-errors. Сегодня я расскажу, зачем я ее написал и почему она может сэкономить вам кучу нервов.
В чем, собственно, боль?
Давайте посмотрим на типичный код джуна (да и не только), который пишет регистрацию юзера:
// Типичный "страшный сон"
app.post('/register', async (req, res) => {
try {
const { email, password } = req.body;
// 1. Ручная валидация
if (!email) return res.status(400).json({ error: 'Email нужен' });
// 2. Сама логика
const user = await User.create({ email, password });
res.status(201).json(user);
} catch (err) {
// 3. Ад обработки ошибок
console.error(err); // Ну хоть в консоль плюнем
if (err.code === 11000) {
return res.status(409).json({ error: 'Такой email уже есть' });
}
if (err.name === 'ValidationError') {
// Тут обычно пишут map, чтобы достать сообщения
return res.status(400).json({ error: 'Ошибка валидации...' });
}
res.status(500).json({ error: 'Всё упало' });
}
});
Это больно читать. Это больно поддерживать. И самое главное — это нужно писать в каждом контроллере.
?) Как я это решил (aka "Магия")
Я хотел, чтобы код выглядел так: «Пробуем сделать действие. Если не вышло — кидаем понятную ошибку. Если база ругается — пусть библиотека сама разбирается».
Вот тот же самый код с использованием моей либы:
const { asyncHandler, Errors } = require('ds-express-errors');
// Оборачиваем в asyncHandler, чтобы забыть про try-catch
app.post('/register', asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Просто делаем запрос. Если email занят, либа сама перехватит ошибку Mongoose,
// поймет, что это дубликат, и отдаст 400-й код с понятным сообщением.
const user = await User.create({ email, password });
// Если нужно кинуть свою ошибку:
if (!user) throw Errors.BadRequest('Не удалось создать');
res.status(201).json(user);
}));
Всё. Никакого шума. Только бизнес-логика.
Что под капотом?
Я написал централизованный errorHandler middleware, который вешается в конце цепочки Express.
Главная фишка — автоматические мапперы. Библиотека смотрит на прилетевшую ошибку и пытается понять, откуда она:
Mongoose: Ловит
ValidationError,CastError(кривой ID) и дубликаты (code 11000).Prisma: Если вы на темной стороне (шучу), библиотека парсит коды P2002, P2003 и другие.
Валидаторы: Поддерживаются Zod и Joi. Ошибки форматируются в одну строчку, типа:
email: Invalid email; age: Too small.JWT: Просроченные или левые токены сразу дают 401 Unauthorized.
Вам не нужно писать if (err instanceof ZodError). Оно просто работает.
🛡 Graceful Shutdown (чтобы не было стыдно перед девопсами)
Когда вы деплоите новую версию, старый под получает сигнал SIGTERM. Если ваше приложение в этот момент просто умрет (как делает process.exit), то все юзеры, у которых прямо сейчас грузился файл или шла транзакция, получат обрыв соединения.
Я встроил в либу утилиту для Graceful Shutdown. Она:
Перестает принимать новые запросы.
Ждет, пока завершатся текущие (через
server.close).Дает вам хук
onShutdown, чтобы закрыть коннекты к БД.Если всё зависло — убивает процесс по таймауту, чтобы не висеть зомби.
Использование элементарное:
const { initGlobalHandlers, gracefulHttpClose } = require('ds-express-errors');
const server = app.listen(3000);
initGlobalHandlers({
closeServer: gracefulHttpClose(server),
onShutdown: async () => {
await mongoose.disconnect(); // Чистим за собой
console.log('БД закрыта, уходим в закат');
}
});
Теперь ваши графики ошибок 5xx при деплое будут чистыми.
📦 Zero Dependencies
Это мой пунктик. Я ненавижу, когда ставишь маленькую либу, а она тянет за собой половину npm.
В package.json у ds-express-errors ровно 0 (ноль) зависимостей в dependencies. Она весит копейки, быстро ставится и не создает дыр в безопасности через third-party пакеты.
При этом она написана на JS, но внутри лежат .d.ts файлы, так что автокомплит в VS Code и поддержка TypeScript работают из коробки.
Как попробовать?
Библиотека доступна в npm.
npm install ds-express-errorsВ коде (обычно в app.js или index.js):
const { errorHandler } = require('ds-express-errors');
// ... ваши роуты ...
// Важно: подключаем в самом конце, ПОСЛЕ всех роутов
app.use(errorHandler);
Итоги
Я писал эту библиотеку в первую очередь для себя, чтобы перестать копипастить код между проектами. Но сейчас она выросла в полноценный инструмент, который закрывает много потребнос��ей при работе с ошибками в Node.js API.
Если вам тоже надоело писать бойлерплейт — попробуйте.
🔗 Доки и сайт: ds-express-errors.dev 🐙 GitHub: ds-express-errors
Буду рад любым звездам, оценкам и issues. Пишите в комменты, если есть идеи, что еще добавить (сейчас думаю над поддержкой TypeORM).
Всем чистого кода и зеленых тестов! 🖖
