Centralized typed error management library for ExpressJS with Zod/Joi/JWT/express-validator and DB (Prisma/Mongoose/Sequelize) support & graceful shutdown
npm install ds-express-errors
DS Express Errors is library for standardizing error handling in Node.js applications built with Express.
It provides ready-to-use error classes (HTTP Presets), a centralized error handler (middleware), automatic: database error mapping (Mongoose, Prisma, Sequelize), validation error mapping (Zod, Joi, express-validator), JWT and built-in simple logging or custom loggers (Winston/Pino).
---
Official website & detailed documentation with examples: ds-express-errors
---
- Ready-to-use HTTP presets: BadRequest, NotFound, Unauthorized, and others, corresponding to standard HTTP codes.
- 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 or Prisma/Sequelize/Zod/Joi validation errors, express-validator) into clear HTTP responses.
- Logging: Built-in logger with levels (Error, Warning, Info, Debug) and timestamps.
- Custom Logger: Easily integrate external loggers like Winston or Pino by passing them into the configuration.
- Security: In production (NODE_ENV=production), stack traces, sensitive data are hidden; visible in development.
- Fully Customizable Response: Adapt the error structure to match your API standards (JSON:API, legacy wrappers, etc.).
- Global Handlers: Optional handling of uncaughtException and unhandledRejection with support for Graceful Shutdown (custom cleanup logic).
- TypeScript support: Includes .d.ts files for full typing support.
---
``bash`
npm install ds-express-errors
---
Add errorHandler at the end of your Express middleware chain.
`js
const express = require('express');
const { errorHandler } = require('ds-express-errors');
const app = express();
// ... your routes ...
// Error handler MUST be after all routes
app.use(errorHandler);
app.listen(3000, () => console.log('Server running...'));
`
---
No need to remember status codes. Just import Errors and use the method you need.
`js
const { Errors } = require('ds-express-errors');
app.get('/users/:id', async (req, res, next) => {
const user = await getUserById(req.params.id);
if (!user) {
// Automatically sends 404 with message "User not found"
return next(Errors.NotFound('User not found'));
}
if (!user.isActive) {
// Automatically sends 403
return next(Errors.Forbidden('Access denied'));
}
res.json(user);
});
`
Create specific errors using the AppError class:
`js
const { AppError } = require('ds-express-errors');
// (message, statusCode, isOperational)
throw new AppError('Custom payment gateway error', 402, true);
`
Avoid repetitive try/catch in every controller.
`js
const { Errors, asyncHandler } = require('ds-express-errors');
const getUser = asyncHandler(async (req, res, next) => {
const data = await database.query();
if (!data) throw Errors.BadRequest('No data');
res.json(data);
});
app.get('/data', getUser);
`
You can explicitly enable handling of global errors (uncaughtException, unhandledRejection). This allows you to log the crash and perform cleanup (like closing server connections) before exiting.
Basic Usage:
Logs the error and exits (process.exit(1)).
`js
const { initGlobalHandlers } = require('ds-express-errors');
// Initialize at the entry point of your app
initGlobalHandlers();
`
DS Express Errors provides a robust way to handle application crashes and termination signals (SIGINT, SIGTERM). It ensures your server stops accepting new connections and finishes active requests before exiting.
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| closeServer | Function | undefined | Async function to close your HTTP server. |onShutdown
| | Function | undefined | Cleanup logic (e.g., disconnect DB) 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 | awaited with a N-second timeout. The library calls process.exit(1) after it completes, so you no longer need to exit manually. |
into a Promise with support for an abort signal.$3
`javascript
const { initGlobalHandlers, gracefulHttpClose } = require('ds-express-errors');initGlobalHandlers({
closeServer: gracefulHttpClose(server), // Gracefully close server
onShutdown: async (signal) => {
console.log('Cleaning up...');
await mongoose.disconnect(); // Close DB connections
},
onCrash: async (err, signal) => {
await sendAlertToAdmin(err); // Notify dev team about crash
}
});
`---
📋 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 & Environment Variables
-
NODE_ENV:
- development — stack trace included in response
- production (or any other) — stack trace hidden, only message and status returned You can define your own dev environment name using
setConfig
$3
-
DEBUG=true — outputs extra debug info about error mapping (mapErrorNameToPreset) You can customize the structure of the error response sent to the client. This is useful if you need to adhere to a specific API standard (e.g., JSON:API) or hide certain fields.
Also you can customize dev environment by using
devEnvironments: []Use
setConfig before initializing the error handler middleware.> Important:
customMappers must be synchronous. Async function or Promise are not supported and will be ignored.`javascript
const { setConfig, errorHandler } = require('ds-express-errors');
const logger = require('./utils/logger'); // Your Winston/Pino logger
const z = require('zod');
const Joi = require('joi');
// Optional: Customize response format and Logger
setConfig({
// (OPTIONAL)
customLogger: logger, // From version v1.8.0+
// (OPTIONAL) You can replace default ds-express-errors check (duck-typing) to more strict by passing error class
// For now is avaliable only Zod, Joi
errorClasses: {
Zod: z,
Joi: Joi
},
// (OPTIONAL) By defalt ds-express-errors use all avaliable mapper, but from v1.8.0+ you can choose only needed mappers
// Mappers ['zod', 'joi', 'mongoose', 'prisma', 'sequelize', 'expressValidator']
needMappers: ['zod', 'joi', 'prisma'], // (In this example) For now library would map only ['zod', 'joi', 'prisma'] errors, other would be
InternalServerError or if is specifieds customMappers it would use that response // ----
// (OPTIONAL) Set prefered log rate per 1 minute
maxLoggerRequests: 1000,
// (OPTIONAL) Define your custom mappers and ds-express-errors would use them first
customMappers: [
(err) => {
if (err.name === 'newError') {
return Errors.BadRequest()
}
}
// ...
],
// (OPTIONAL)
devEnvironments: ['development', 'dev'],
// (OPTIONAL)
formatError: (err, {req, isDev}) => {
return {
success: false,
error: {
code: err.statusCode,
message: err.message,
...(isDev ? { debug_stack: err.stack } : {})
}
};
}
});
const app = express();
// ... your routes ...
app.use(errorHandler);
`$3
By default if you not set
customLogger in setConfig library used his own logger> Library logger have rate logging limits support
| Loggers | params | Output color |
|------------|-------------------------|---------------|
| logError | error, req (optional) | red |
| logWarning | message, req (optional) | yellow |
| logInfo | message | default white |
| logDebug | message, req (optional) | blue |
$3
You can connect your own logger (like Winston, Pino) instead of the built-in console logger.
The object must support 4 methods:
error, warn, info, debug.`javascript
const { setConfig } = require('ds-express-errors');
const winston = require('winston'); // Exampleconst logger = winston.createLogger({
// ... your winston config
});
// Pass your logger instance
setConfig({
customLogger: logger
});
`Default Response Format
If no config is provided, the library uses the default format:
`json
{
"status": "error", // or 'fail'
"method": "GET", // showed when NODE_ENV= development or dev
"url": "/api/resource", // showed when NODE_ENV= development or dev
"message": "Error description",
"stack": // showed when NODE_ENV= development or dev
}`Default Config Format
`javascript
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 automatically maps non-AppError instances (e.g., database errors) to HTTP responses.Supported mappings:
- JWT:
JsonWebTokenError, TokenExpiredError, NotBeforeError → mapped to 401 Unauthorized
- express-validator: (v1.7.0+) FieldValidationError, GroupedAlternativeValidationError, AlternativeValidationError → mapped to 422 Unprocessable Content and UnknownFieldsError → mapped to 400 Bad Request
- Validation Libraries: ZodError (Zod), ValidationError (Joi) — automatically formatted into readable messages.
- Mongoose / MongoDB: CastError, DuplicateKeyError (code 11000), ValidationError, MongoServerError is handled (400 for bad JSON body, 500 for code errors, 409 colflict).
- Prisma: PrismaClientKnownRequestError, PrismaClientUnknownRequestError, PrismaClientRustPanicError, PrismaClientInitializationError, PrismaClientValidationError
- Sequelize: SequelizeUniqueConstraintError, SequelizeValidationError, SequelizeForeignKeyConstraintError, SequelizeOptimisticLockError, SequelizeEmptyResultError, SequelizeDatabaseError, SequelizeConnectionError, SequelizeTimeoutError
- JS Native: ReferenceError, TypeError → mapped to 500. SyntaxError is handled (400 for bad JSON body, 500 for code errors).---
Supported Prisma Error Codes:
| Error Code | Dev Message | Prod Message | HTTP Status |
| ---------- | ----------------------------- | --------------------- | ----------- |
| P2000 | Value too long for column: ... | Invalid input value | 400 |
| P2001 | Record does not exist: ... | Resource not found | 404 |
| P2002 | Unique constraint failed: ... | Conflict | 409 |
| P2003 | Foreign key constraint failed: ... | Invalid reference | 400 |
| P2005 | The value stored in the database for the field is invalid for the field's type: ... | Invalid data provided | 400 |
| P2006 | The provided value for the field is not valid: ... | Invalid input value | 400 |
| P2007 | Data validation error: ... | Invalid reference | 400 |
| P2011 | Foreign key constraint failed: ... | Invalid request data | 400 |
| P2014 | Required relation violation: ... | Invalid relation | 400 |
| P2015 | Null constraint violation: ... | Required data is missing | 404 |
| P2021 | Table does not exist: ... | Internal server error | 500 |
| P2022 | Column does not exist: ... | Internal server error | 500 |
| P2025 | Record not found: ... | Resource not found | 404 |
| P2027 | Foreign key constraint failed: ... | Invalid reference | 500 |
| P1001 | Cannot reach database: ... | Service unavailable | 503 |
| P1002 | Database timeout: ... | Service unavailable | 503 |
| P1003 | Database does not exist: ... | Internal server error | 500 |
Supported Sequelize errors:
| Error Code / Type | Prod Message | HTTP Status |
|------------------|-------------|-------------|
| SequelizeValidationError | validation error | 400 |
| SequelizeUniqueConstraintError | Resource already exists | 409 |
| SequelizeForeignKeyConstraintError | invalid references | 409 |
| SequelizeOptimisticLockError | Resource conflict occurred | 409 |
| SequelizeEmptyResultError | Resource not found | 404 |
| SequelizeDatabaseError | Database error occurred | 500 |
| SequelizeConnectionError | Database connection error occurred | 503 |
| SequelizeTimeoutError | Database timeout error occurred | 504 |
📝 Example Client Response
Development mode:
`json
{
"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:
`json
{
"status": "error",
"message": "User not found"
}``