A lightweight, configurable error handling middleware, designed specifically for MERN backends. Simple and easy to use, catch and format all expected errors into consistent and client friendly responses!
Preventing accidental exposure of stack traces right out of the box, this middleware package catches and formats errors from Express, Mongoose/MongoDB, JWT, ZOD and custom app errors. Customizable configurations allow extremely easy to set options for console logging, using external logging packages (e.g Winston, Pino etc.), logging stack traces, and incorporating your own custom error handlers.
- 🧩 Features
- 📦 Installation
- ⚙️ Configuration Options
- 💻 Usage
- 📤 Response Format
⚠️ Handled Error Types- ✍️ Custom Error Usage
- 🚨 Unhandled Errors
- 🧪 Testing
- 🔗 Links
- 🔒 Prevents stack trace leaks - Errors are formatted into client friendly stack free messages, with optional logging of stack traces.
- 📦 Zero configuration - Sensible default configurations with logging based on current environment
- 🧰 Easy Configuration - Set logging, stack trace exposure, and custom logger options
- 🎯 Consistent responses - Uses the popular response format (success, message, errors), allowing for easy client integration
- 🌐 Environment-aware - Automatically adjusts behavior based on
NODE_ENVenvironment variables - ✅ Comprehensive coverage - Handles Express, Mongoose, JWT, and Zod errors
- 🤝 Accepts custom error-handlers - Provide your own error handlers in an array for seamless integration
- 🦾 ESM, CommonJS and TypeScript support - Supports ESM and CJS imports, with TS type support
npm install express-mongo-error-handler| Option | Type | Default | Description |
|---|---|---|---|
logErrors |
Boolean |
true in test/development, false otherwise |
true enables error logging |
exposeStack |
Boolean |
false |
true includes stack traces in logs |
logger |
Function |
console.error |
Enables use of custom logging packages (examples in usage) |
customHandlers |
Array<Function> |
[] |
Enables integration of custom error handlers (examples in usage) |
import express from "express";
import createErrorHandler from "express-mongo-error-handler"; // ESM
// OR
const createErrorHandler = require("express-mongo-error-handler"); // CJS
const app = express();
// Your routes here
app.get("/api/example", (req, res) => {
/* Route logic */
});
/* ADD LAST! After all existing middleware and routes.
Default configuration logs errors only in test or development environments. Stack traces are not
logged by default, and the default logger used is console.error() */
const errorHandler = createErrorHandler();
app.use(errorHandler);const errorHandler = createErrorHandler({
logErrors: true, // Default true in 'test' and 'development' environments, false in 'production'
exposeStack: true, // Default value is false
});
app.use(errorHandler);Winston:
import winston from "winston";
const logger = winston.createLogger({
/* config */
});
const errorHandler = createErrorHandler({ logger: logger.error.bind(logger) });
app.use(errorHandler);Pino:
Pino expects reversed order of message and data
import pino from "pino";
const logger = pino({
/* config */
});
// Swap order for message and data
const errorHandler = createErrorHandler({ logger: (msg, data) => logger.error(data, msg) });
app.use(errorHandler);Bunyan:
Bunyan expects reversed order of message and data
import bunyan from "bunyan";
const logger = bunyan.createLogger({
/* config */
});
// Swap order for message and data
const errorHandler = createErrorHandler({ logger: (msg, data) => logger.error(data, msg) });
app.use(errorHandler);const errorHandler = createErrorHandler({ logErrors: false });
app.use(errorHandler);Custom handlers passed to middleware will run before built-in error handling
// Array of custom error handler functions for adding additional package compatibility
const customHandlers = [
(err, req, res) => {
// Example 'stripe' package error handler
if (err.type === "StripeCardError") {
return res.status(402).json({
success: false,
message: "Payment failed",
errors: [err.message],
});
}
},
// Each 'if' block can be a separate function or wrapped in a single function as desired
(err, req, res) => {
// Example 'multer' package error handler
if (err.name === "MulterError") {
return res.status(400).json({
success: false,
message: "File upload error",
errors: [err.message],
});
}
},
/* Additional error handlers as required */
];
let errorHandler = createErrorHandler({ customHandlers }); // Pass handlers to config
app.use(errorHandler);All errors are formatted into a consistent, client-friendly structure. Errors are always in an array:
{
"success": false,
"message": "Basic error message",
"errors": ["More detailed error message in array"]
}Many mongoose error messages are objects with a field and message property:
{
"success": false,
"message": "Duplicate key violation",
"errors": [
{ "field": "email", "message": "Record with field 'email' already exists" },
{ "field": "username", "message": "Record with field 'username' already exists" }
]
}| Error | Status | When it occurs |
|---|---|---|
SyntaxError (JSON) |
400 | Malformed/invalid JSON in request body |
entity.too.large |
413 | Request body data exceeds max size limit |
URIError |
400 | Malformed/invalid URI component encoding |
| Error | Status | When it occurs |
|---|---|---|
ValidationError |
400 | Mongoose schema validation violation |
Duplicate key (11000) |
409 | Unique constraint violation |
CastError |
400 | Invalid ObjectId or type cast |
DocumentNotFoundError |
404 | Document not found (e.g. when using .orFail()) |
StrictModeError |
400 | Unknown schema field when using strict mode |
VersionError |
409 | Concurrent modification conflict |
ParallelSaveError |
409 | Parallel save on same document |
MongooseServerSelectionError |
503 | Database connection failure |
MongoNetworkError |
503 | Database network error |
| Error | Status | When it occurs |
|---|---|---|
JsonWebTokenError |
401 | Invalid/malformed token |
TokenExpiredError |
401 | Token has expired |
NotBeforeError |
401 | Token not yet active |
| Error | Status | When it occurs |
|---|---|---|
ZodError |
400 | Zod schema validation fails |
Errors with a statusCode property are handled automatically. Example:
// Creating custom error in route
const error = new Error("Resource not found");
error.statusCode = 404;
// errors array is optional, automatically attaches error message if missing
error.errors = ["The requested user does not exist"];
throw error;Any missed or unhandled errors are still caught with generic catch all, returning 500 status.
Any errors thrown with a statusCode property will be caught and handled. Example:
// Creation of custom error class. Must contain statusCode property to be handled appropriately
class CustomError extends Error {
constructor(message, statusCode, errors = []) {
super(message);
this.statusCode = statusCode;
this.errors = errors.length ? errors : [message];
}
}
// Example usage in routes
app.get("/api/users/:id", async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
throw new CustomError("User not found", 404);
}
res.json(user);
} catch (error) {
next(error);
}
});Some errors are not runtime issues and are deliberately ignored. Catching these could mask issues that should be caught and fixed in development, such as:
MissingSchemaError- Schema hasn't been properly configuredOverwriteModelError- Naming conflict between modelsStrictPopulateError- Schema field being populated isn't defined or allowedDivergentArrayError- Caused by conflicting array modifications. Indicates bad update logic
npm run test