deep-express-errors
v1.9.3
Published
Centralized typed error management library for ExpressJS with Zod/Joi/JWT/express-validator and DB (Prisma/Mongoose/Sequelize) support & graceful shutdown
Maintainers
Readme
Deep Express Errors
Deep Express Errors is a library for standardizing error handling in Node.js applications built with Express. It provides ready-to-use error classes, a centralized error handler middleware, automatic database error mapping, validation error mapping, JWT handling, and built-in logging or custom logger support.
Features
- Ready-to-use HTTP presets:
BadRequest,NotFound,Unauthorized, and others. - Centralized handling: one middleware catches all errors and formats them into a unified JSON response.
- Automatic mapping: converts native errors like JWT, MongoDB duplicate key errors, Prisma, Sequelize, Zod, Joi, and express-validator errors into clear HTTP responses.
- Logging: built-in logger with levels and timestamps.
- Custom logger: integrate Winston, Pino, or another logger by passing it into the configuration.
- Security: in production, stack traces and sensitive data are hidden.
- Fully customizable response: adapt the error structure to match your API standards.
- Global handlers: optional handling of
uncaughtExceptionandunhandledRejectionwith graceful shutdown support. - TypeScript support: includes
.d.tsfiles for full typing support.
Installation
npm install deep-express-errorsIntegration
Add errorHandler at the end of your Express middleware chain.
const { errorHandler } = require('deep-express-errors');Example:
const express = require('express');
const { errorHandler } = require('deep-express-errors');
const app = express();
// ... your routes ...
// Error handler MUST be after all routes
app.use(errorHandler);
app.listen(3000, () => console.log('Server running...'));Warning If you use only
errorHandler, the library performs duck-typing checks.
Want strict checks? Visit the configuration section and use the
errorClassesoption.
Usage
Throwing Errors with Presets
const { Errors } = require('deep-express-errors');
app.get('/users/:id', async (req, res, next) => {
const user = await getUserById(req.params.id);
if (!user) {
return next(Errors.NotFound('User not found'));
}
if (!user.isActive) {
return next(Errors.Forbidden('Access denied'));
}
res.json(user);
});Async Function Wrapper
const { Errors, asyncHandler } = require('deep-express-errors');
const getUser = asyncHandler(async (req, res, next) => {
const data = await database.query();
if (!data) return next(Errors.BadRequest('No data'));
res.json(data);
});
app.get('/data', getUser);Custom Errors with AppError
const { AppError } = require('deep-express-errors');
throw new AppError('Custom payment gateway error', 402, true);Graceful Shutdown and Global Handlers
You can enable handling of global errors (uncaughtException, unhandledRejection) and termination signals (SIGINT, SIGTERM) so the app can log the crash and perform cleanup before exiting.
initGlobalHandlers(options)
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| closeServer | Function | undefined | Async function to close your HTTP server. |
| onShutdown | Function | undefined | Cleanup logic during normal exit. |
| onCrash | Function | undefined | Cleanup logic during uncaughtException or unhandledRejection. |
| exitOnUnhandledRejection | Boolean | true | Exit process after rejection. |
| exitOnUncaughtException | Boolean | true | Exit process after exception. |
| maxTimeout | number | 10000 | Timeout in milliseconds for awaited cleanup. |
gracefulHttpClose(server)
A helper that wraps server.close() into a Promise with support for an abort signal.
Example usage
const { initGlobalHandlers, gracefulHttpClose } = require('deep-express-errors');
initGlobalHandlers({
closeServer: gracefulHttpClose(server),
onShutdown: async () => {
console.log('Cleaning up...');
await mongoose.disconnect();
},
onCrash: async (err) => {
await sendAlertToAdmin(err);
}
});Available Error Presets
All methods are available via the Errors object. Default isOperational is true.
| Method | Status Code | Description |
|--------|-------------|-------------|
| Errors.BadRequest(message) | 400 | Bad Request |
| Errors.Unauthorized(message) | 401 | Unauthorized |
| Errors.PaymentRequired(message) | 402 | Payment Required |
| Errors.Forbidden(message) | 403 | Forbidden |
| Errors.NotFound(message) | 404 | Not Found |
| Errors.Conflict(message) | 409 | Conflict |
| Errors.UnprocessableContent(message) | 422 | Unprocessable Content |
| Errors.TooManyRequests(message) | 429 | Too Many Requests |
| Errors.InternalServerError(message) | 500 | Internal Server Error |
| Errors.NotImplemented(message) | 501 | Not Implemented |
| Errors.BadGateway(message) | 502 | Bad Gateway |
| Errors.ServiceUnavailable(message) | 503 | Service Unavailable |
| Errors.GatewayTimeout(message) | 504 | Gateway Timeout |
Configuration and Environment Variables
NODE_ENV:development- stack trace included in responseproductionor any other value - stack trace hidden, onlymessageandstatusreturned
DEBUG=true- outputs extra debug info about error mapping.
You can customize the response format and environment behavior with setConfig.
Important
customMappersmust be synchronous. Async functions and Promises are ignored.
const { setConfig, errorHandler } = require('deep-express-errors');
const logger = require('./utils/logger');
const z = require('zod');
const Joi = require('joi');
const { Sequelize } = require('sequelize');
const { Prisma } = require('@prisma/client');
setConfig({
customLogger: logger,
errorClasses: {
Zod: z,
Joi: Joi,
Prisma,
Sequelize
},
needMappers: ['zod', 'joi', 'prisma'],
maxLoggerRequests: 1000,
customMappers: [
(err) => {
if (err.name === 'newError') {
return Errors.BadRequest();
}
}
],
devEnvironments: ['development', 'dev'],
formatError: (err, { req, isDev }) => {
return {
success: false,
error: {
code: err.statusCode,
message: err.message,
...(isDev ? { debug_stack: err.stack } : {})
}
};
}
});
const app = express();
app.use(errorHandler);Library Logger
By default, if customLogger is not set, the library uses its own logger.
| Logger | Params | Output color |
|--------|--------|--------------|
| logError | error, req (optional) | red |
| logWarning | message, req (optional) | yellow |
| logInfo | message | default white |
| logDebug | message, req (optional) | blue |
Custom Logger
const { setConfig } = require('deep-express-errors');
const winston = require('winston');
const logger = winston.createLogger({
// your config
});
setConfig({
customLogger: logger
});Default Response Format
{
"status": "error",
"method": "GET",
"url": "/api/resource",
"message": "Error description",
"stack": "..."
}Default Config Format
let config = {
customMappers: [],
customLogger: null,
errorClasses: null,
needMappers: null,
maxLoggerRequests: 100,
devEnvironments: ['dev', 'development'],
formatError: (err, { req, isDev }) => ({
status: err.isOperational ? 'fail' : 'error',
message: err.message,
...(isDev ? {
method: req.method,
url: req.originalUrl,
stack: err.stack
} : {})
})
}Third-Party Error Mapping
mapErrorNameToPreset maps non-AppError instances to HTTP responses.
Supported mappings:
- JWT:
JsonWebTokenError,TokenExpiredError,NotBeforeError->401 Unauthorized - express-validator:
FieldValidationError,GroupedAlternativeValidationError,AlternativeValidationError->422 Unprocessable Content - Validation libraries:
ZodError,ValidationErrorfrom Joi - Mongoose / MongoDB:
CastError, duplicate key errors, validation errors, andMongoServerError - Prisma:
PrismaClientKnownRequestError,PrismaClientUnknownRequestError,PrismaClientRustPanicError,PrismaClientInitializationError,PrismaClientValidationError - Sequelize:
SequelizeUniqueConstraintError,SequelizeValidationError,SequelizeForeignKeyConstraintError,SequelizeOptimisticLockError,SequelizeEmptyResultError,SequelizeDatabaseError,SequelizeConnectionError,SequelizeTimeoutError,SequelizeConnectionRefusedError,SequelizeHostNotFoundError,SequelizeHostNotReachableError,SequelizeAccessDeniedError - JavaScript native errors:
ReferenceError,TypeError, andSyntaxError
Example Client Response
Development mode
{
"status": "error",
"method": "GET",
"url": "/api/users/999",
"message": "User not found",
"stack": "Error: User not found\n at /app/controllers/user.js:15:20..."
}Production mode
{
"status": "error",
"message": "User not found"
}