npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ds-express-errors

v1.9.2

Published

Centralized typed error management library for ExpressJS with Zod/Joi/JWT/express-validator and DB (Prisma/Mongoose/Sequelize) support & graceful shutdown

Readme

📦 DS Express Errors NPM Downloads

DS Express Errors is a 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


✨ Features

  • 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.

🚀 Installation

npm install ds-express-errors

🛠 Integration

Add errorHandler at the end of your Express middleware chain.

const { errorHandler } = require('ds-express-errors');

If you need examples in ESM not commonjs please visit ds-express-errors

[!WARNING]

Good to know

If you use only errorHandler the library would perform only duck-typing checks.

[!TIP]

Want strict checks?

Visit the Configuration section. There you will find the errorClasses config property.

Example:

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...'));

[!IMPORTANT]

About Environment and Prod vs Dev Responses

By default, the library relies on the NODE_ENV environment variable to format error responses. To see how Production and Development responses differ, visit the Configuration See the devEnvironments property to customize this behavior.


📖 Usage

1. Throwing Errors (Using Presets)

No need to remember status codes. Just import Errors and use the method you need.

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);
});

2. Async Function Wrapper (asyncHandler)

Avoid repetitive try/catch in every controller.

const { Errors, asyncHandler } = require('ds-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);

3. Using AppError (Custom Errors)

Create specific errors using the AppError class:

const { AppError } = require('ds-express-errors');

// (message, statusCode, isOperational)
throw new AppError('Custom payment gateway error', 402, true);

4. Global Process Handlers (Graceful Shutdown)

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)).

[!TIP]

More detailed information

Visit the Global Handlers and Graceful Shutdown.

const { initGlobalHandlers } = require('ds-express-errors');

// Initialize at the entry point of your app
initGlobalHandlers();

Graceful Shutdown & Global Handlers (v1.8.0+)

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.

initGlobalHandlers(options)

| 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. |

gracefulHttpClose(server)

A helper that wraps server.close() into a Promise with support for an abort signal.

Example usage

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
  }
});

[!IMPORTANT]

Be aware

initGlobalHandlers can only be called once. Calling it again will throw a GlobalHandlerAlreadySet error.

GlobalHandlerAlreadySet is an internal ds-express-errors error with the code ERR_DS_EXPRESS_ERRORS_GLOBAL_HANDLER_ALREADY_SET.


📋 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 |

[!TIP]

More detailed information

Visit the API REFERENCES.


⚙️ 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

[!IMPORTANT]

About Environment and Prod vs Dev Responses

If NODE_ENV is not defined, you will receive sanitised error messages, as the library behaves as if the environment were set to production. To see how Production and Development responses differ, visit the Configuration See the devEnvironments property to customize which environments should display full error information (stack traces and full error messages).

⚙️ Configuration

  • 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]

Be aware

setConfig can only be called once. Calling it again will throw a ConfigAlreadySet error.

ConfigAlreadySet is an internal ds-express-errors error with the code ERR_DS_EXPRESS_ERRORS_CONFIG_ALREADY_SET.

[!IMPORTANT] customMappers must be synchronous. Async function or Promise are not supported and will be ignored.

const { setConfig, errorHandler } = require('ds-express-errors');
const logger = require('./utils/logger'); // Your Winston/Pino logger
const z = require('zod');
const Joi = require('joi');
const { Sequelize } = require('sequelize');

const { PrismaClient, Prisma  } = require('@prisma/client');
// 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 available only Zod, Joi, Sequelize, Prisma
    errorClasses: {
      Zod: z,
      Joi: Joi,
      Prisma,
      Sequelize
    },

    // (OPTIONAL) By default  ds-express-errors use all available 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 specified `customMappers` it would use that response

    // ----
    
    // (OPTIONAL) Set preferred 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);

🔌 Library logger

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 |

🔌 Custom Logger

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.

const { setConfig } = require('ds-express-errors');
const winston = require('winston'); // Example

const 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:

{
  "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
}

[!TIP]

Want a custom response format?

Define the formatError property in setConfig. More in Configuration

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 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, SequelizeConnectionRefusedError, SequelizeHostNotFoundError, SequelizeHostNotReachableError, SequelizeAccessDeniedError
  • 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: ... | Resource already exists | 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 | 400 | | P2006 | The provided value for the field is not valid: ... | Invalid input value | 400 | | P2007 | Data validation error: ... | Invalid reference | 400 | | P2011 | Null constraint violation: ... | Required value is missing | 400 | | P2014 | Required relation violation: ... | Invalid relation | 400 | | P2015 | A related record could not be found: ... | Requested resource not found | 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 | Multiple errors occurred on the database during query execution: ... | Internal server error | 500 | | P1001 | Cannot reach database: ... | Service unavailable | 503 | | P1002 | Database timeout: ... | Service unavailable | 503 | | P1003 | Database does not exist: ... | Internal server error | 500

[!NOTE]

Example Prisma output for dev:

[2026-07-23T12:01:34.442Z] POST /prisma/p2003 
MESSAGE: Prisma P2003: [PrismaClientKnownRequestError] Foreign key constraint failed: { modelName: Post }; { field_name: Post_authorId_fkey (index) }  Operation: `prisma.post.create()` 
StatusCode: 400 
Stack: Error: Prisma P2003: [PrismaClientKnownRequestError] Foreign key constraint failed: { modelName: Post }; { field_name: Post_authorId_fkey (index) } 
Operation: `prisma.post.create()`
at BadRequest C:\...
Operational: true

Supported Sequelize errors:

| Error Code / Type | Prod Message | HTTP Status | |------------------|-------------|-------------| | SequelizeValidationError | email must be unique | 400 | | SequelizeUniqueConstraintError | Resource already exists | 409 | | SequelizeForeignKeyConstraintError | Invalid reference | 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 | | SequelizeConnectionRefusedError | Database connection error occurred | 503 | | SequelizeHostNotFoundError | Database connection error occurred | 503 | | SequelizeHostNotReachableError | Database connection error occurred | 503 | | SequelizeAccessDeniedError | Database connection error occurred | 503 |

[!NOTE]

Example Sequelize output for dev:

[2026-07-23T13:02:44.783Z] POST /sequelize/unique-constraint 
MESSAGE: Sequelize: [UniqueConstraintError]: sku must be unique 
StatusCode: 409 
Stack: Error: Sequelize: [UniqueConstraintError]: sku must be unique
at Conflict C:...
Operational: true

📝 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"
}