Configuration


1. Overview

The configuration system is accessed via setConfig(options).
All configuration must be provided before the error handler middleware is initialized.

2. Default Configuration Interface

The setConfig function accepts a single object parameter with optional properties. The configuration is stored in a module-level singleton and affects all subsequent error handling operations.

Default configuration settings:

When setConfig is not called, the library uses the following default configuration defined in

javascript
setConfig({
    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
        } : {})
    })
})

3. Configuration Options Reference

customMappers

Type: ErrorMapper[]

Default: []

Validation: Must be an array of synchronous functions (no async functions or Promises)

An ordered array of custom error mapping functions executed before built-in mappers. Each mapper receives an error and request object, returning either a transformed AppError or undefined/null to pass to the next mapper.

Interface:

typescript
interface ErrorMapper {
    (err: AppError | Error, req: Request): AppError | Error | undefined | null;
}

Execution Order:

  1. Custom mappers (array order)
  2. Library-specific mappers (filtered by needMappers)
  3. Name-based mapper (native errors)
  4. Fallback (500 Internal Server Error)

Example:

javascript
setConfig({
    customMappers: [
        (err) => {
            if (err.name === 'PaymentGatewayError') {
                return Errors.PaymentRequired('Payment processing failed');
            }
        },
        (err) => {
            if (err.code === 'ECONNREFUSED') {
                return Errors.ServiceUnavailable('External service unavailable');
            }
        }
    ]
});

errorClasses

Type: ErrorClasses | null

Default: null

Validation: Must be an object

Provides error constructor references for strict instanceof checks instead of duck-typing.
Currently (v1.8.1+) supports Zod and Joi validation libraries.

Interface:

typescript
interface ErrorClasses {
    Zod?: { ZodError: new (...args: any[]) => Error }
    Joi?: { ValidationError: new (...args: any[]) => Error }
}

Without errorClasses, the library uses duck-typing to detect error types (checking for properties like error.issues for Zod).
With errorClasses, the library performs strict instanceof checks for more reliable type detection.

Example:

javascript
import { zod } from 'zod'
import Joi from 'joi'

setConfig({
    errorClasses: {
        Zod: z,
        Joi: Joi
    }
});

needMappers

Type: string[] | null

Default: null (all mappers enabled)

Validation: Must be an array

Enables selective activation of library-specific error mappers, reducing overhead when certain libraries are not used in the application.

Valid Mapper Names:

  • 'zod' - Zod validation errors
  • 'joi' - Joi validation errors
  • 'expressValidator' - express-validator errors
  • 'mongoose' - Mongoose/MongoDB errors
  • 'prisma' - Zod validation errors
  • 'sequelize' - Sequelize ORM errors

When needMappers is null, all mappers are active. When specified, only listed mappers execute. Custom mappers and name-based mappers always execute regardless of this setting.

Example:

javascript
setConfig({
    needMappers: ['zod', 'prisma'] // Only map Zod and Prisma errors
});

customLogger

Type: Logger | null

Default: null

Validation: Must be an object with methods error, warn, info, debug

Replaces the built-in console logger with a custom logging implementation. When provided, all logging calls are delegated to this logger instead of the internal logger.

Interface:

typescript
export interface Logger {
    error(message: any, ...args: any[]): void
    warn(message: any, ...args: any[]): void
    info(message: any, ...args: any[]): void
    debug(message: any, ...args: any[]): void
} 

Example:

javascript
import winston from 'winston'

const levels = {
  error: 0,
  warn: 1,
  info: 2,
  debug: 3,
};

const logger = winston.createLogger({
    level: levels,
    format: winston.format.json(),
    transports: [new winston.transports.File({ filename: 'error.log' })]
});

setConfig({ customLogger: logger });

maxLoggerRequests

Type: number

Default: 100

Validation: Must be a number

Sets the maximum number of log entries allowed per minute. This rate limiting prevents log flooding in high-error scenarios, protecting log storage and performance.

When the limit is exceeded, additional log requests are silently dropped until the next minute window.

Example:

javascript
setConfig({
    maxLoggerRequests: 1000 // Allow 1000 logs per minute
});

devEnvironments

Type: string[]

Default: ['dev', 'development']

Validation: Must be a array

Defines which NODE_ENV values are considered development environments. This controls error verbosity and stack trace inclusion in responses.

Checks if process.env.NODE_ENV matches any value in this array. When true:

  • Full stack traces included in responses
  • Request method and URL exposed
  • Detailed validation error messages
  • Database field names revealed

Example:

javascript
setConfig({
    devEnvironments: ['development', 'dev', 'staging', 'local']
});

formatError

Type: (err: AppError | Error, options: ConfigOptions) => any

Default: See below

Validation: Must be a function

Custom formatter for the final error response sent to clients. This enables adherence to API standards like JSON:API or legacy response formats.

Interface:

typescript
interface ConfigOptions {
    req: Request
    isDev: boolean
}

type FormatError = (err: AppError | Error, options: ConfigOptions) => any

Default Implementation:

javascript
formatError: (err, {req, isDev}) => ({ 
    status: err.isOperational ? 'fail' : 'error',
    message: err.message,
    ...(isDev ? { 
        method: req.method,
        url: req.originalUrl,
        stack: err.stack
    } : {})
})

The formatter receives the error object (which is always an AppError instance after mapping) and a context object containing the Express request and environment flag.

Example (JSON:API format):

javascript
setConfig({
    formatError: (err, {req, isDev}) => ({
        errors: [{
            status: err.statusCode.toString(),
            title: err.message,
            detail: isDev ? err.stack : undefined,
            source: isDev ? {
                pointer: req.originalUrl
            } : undefined
        }]
    })
});

4. Configuration Timing

Configuration must be set before the error handler middleware is registered:

javascript
import express from 'express'
import { setConfig, errorHandler } from 'ds-express-errors'

const app = express();

// 1. Set configuration FIRST
setConfig({
    customLogger: winston.createLogger({...}),
    devEnvironments: ['development', 'staging'],
    maxLoggerRequests: 500
});

// 2. Register routes
app.get('/api/users', ...);

// 3. Register error handler LAST
app.use(errorHandler);

app.listen(3000);

Once errorHandler is invoked, it reads from the configuration singleton. Calling setConfig after error handler registration affects subsequent errors but not errors already in flight.