devdad-express-utils
v1.8.2
Published
Reusable Express.js utilities for error handling, async wrapping, and more
Maintainers
Readme
Express Utils
A collection of reusable utilities for Express.js applications, including error handling, async route wrapping, custom error classes, MongoDB connection management, and Winston-based logging.
Installation
npm install devdad-express-utils
# or
yarn add devdad-express-utils
# or
pnpm add devdad-express-utilsUsage
Error Handling
import express from "express";
import { errorHandler, AppError } from "devdad-express-utils";
const app = express();
// Your routes here
// Use the error handler as the last middleware
app.use(errorHandler);
// In your controllers, throw AppError for operational errors
throw new AppError("Something went wrong", 400);Async Route Wrapping
import { catchAsync } from "devdad-express-utils";
const getUsers = catchAsync(async (req, res, next) => {
const users = await User.find();
res.json(users);
});
app.get("/users", getUsers);Custom Error Class
import { AppError } from "devdad-express-utils";
throw new AppError("Validation failed", 400, ["Email is required"]);Response Formatting
Standardize API responses with consistent JSON structure and callback-based chaining.
import { sendSuccess, sendError } from "devdad-express-utils";
// Basic success response
sendSuccess(res, { id: 1, name: "John" }, "User fetched", 200);
// Chain methods using callbacks
sendSuccess(res, { id: 1, name: "John" }, "User fetched", 200, [
(res) => res.cookie("session", "abc123"),
(res) => res.setHeader("X-Custom", "value")
]);
// Error response (sends immediately)
sendError(res, "User not found", 404);Response Format
All responses include a success field by default:
Success Response:
{
"status": "success",
"success": true,
"message": "User fetched",
"data": { "id": 1, "name": "John" }
}Error Response:
{
"status": "error",
"success": false,
"message": "User not found"
}Method Chaining
sendSuccess returns the Express response object for native chaining:
sendSuccess(res, { token: "abc123" }, "Login successful", 200)
.cookie("authToken", "abc123", { httpOnly: true })
.header("X-Rate-Limit-Remaining", "99")
.status(201); // Can even override status
send(res); // Send the prepared responseDatabase Connection
MongoDB connection utility with automatic reconnection, exponential backoff, and configurable retry logic.
import {
connectDB,
getDBStatus,
resetDBConnection,
} from "devdad-express-utils";
// Connect to MongoDB (ensure MONGO_URI is set in environment)
await connectDB();
// Check connection status
const status = getDBStatus();
console.log(status); // { isConnected: true, readyState: 1, host: '...', name: '...', retryCount: 0 }
// Manually reset and retry (useful after Docker container restarts)
await resetDBConnection();Environment Variables
- MONGO_URI: MongoDB connection string (required)
- DB_MAX_RETRIES: Maximum connection retry attempts (default: 10)
- DB_RETRY_INTERVAL: Initial retry interval in milliseconds (default: 3000)
- NODE_ENV: Set to 'production' to exit after max retries, 'development' to keep process alive
Retry Behavior
- Exponential backoff: Retry intervals increase exponentially (3s → 6s → 12s → 24s → 30s max)
- Random jitter: Adds up to 1 second of random delay to prevent thundering herd
- Docker-friendly: Higher default retry count (10) accommodates container startup times
- Development mode: Process stays alive after max retries for manual recovery
- Production mode: Process exits after max retries to allow container restart
Logging
Winston-based logger with configurable service name and environment-aware transports.
import { logger } from "devdad-express-utils";
// Log messages at different levels
logger.info("User logged in", { userId: 123 });
logger.error("Database connection failed", { error: err.message });
logger.debug("Processing request", { requestId: "abc-123" });Configuration
- Service Name: Set
SERVICE_NAMEenvironment variable to customize the service name in logs (defaults to "express-utils") - Log Level: "debug" in development, "info" in production
- Transports:
- Development: Console (colored) + error.log + combined.log files
- Production: Console only (suitable for platforms like Railway)
Log Files
In development, logs are written to:
error.log: Error level and abovecombined.log: All log levels
Error Handling Patterns
Using AppError
For operational errors (expected errors like validation):
// In controllers wrapped with catchAsync
const createUser = catchAsync(async (req, res, next) => {
// Validation fails
return next(new AppError("Email is required", 400));
});
// Or for unexpected errors
throw new AppError("Database connection failed", 500);Why next(new AppError()) over throw?
next()passes the error to your error handler middleware- Allows centralized error handling and formatting
- Better for Express middleware pattern
throwis more for unexpected errors that bubble up
Complete Example
const express = require("express");
const { AppError, catchAsync, errorHandler } = require("devdad-express-utils");
const app = express();
const getUser = catchAsync(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new AppError("User not found", 404));
}
res.json(user);
});
app.get("/users/:id", getUser);
// Error handler should be last
app.use(errorHandler);JavaScript Usage
const { AppError, catchAsync, errorHandler } = require("devdad-express-utils");
// Or with ES modules
import { AppError, catchAsync, errorHandler } from "devdad-express-utils";API
AppError
Custom error class for operational errors.
new AppError(message: string, statusCode: number, errors?: string[])catchAsync
Higher-order function to wrap async route handlers and catch errors.
catchAsync(fn: (req, res, next) => Promise<any>) => (req, res, next) => voiderrorHandler
Express error handling middleware with detailed logging in development.
errorHandler(err: any, req: Request, res: Response, next: NextFunction) => voidsendSuccess
Sends a standardized success response with optional callbacks for chaining response methods.
sendSuccess(res: Response, data: any, message?: string, statusCode?: number, callbacks?: Array<(res: Response) => Response>) => ResponseFeatures:
- Includes
success: truefield by default - Supports chaining via callbacks array
- Executes callbacks before sending response
- No timing issues - predictable order
- Clean and explicit approach
sendError
Sends a standardized error response immediately.
sendError(res: Response, message: string, statusCode?: number, data?: any) => voidFeatures:
- Includes
success: falsefield by default - Sends response immediately (no chaining needed for errors)
- Supports optional error data
connectDB
Connects to MongoDB with retry logic and automatic reconnection.
connectDB() => Promise<void>getDBStatus
Gets the current MongoDB connection status.
getDBStatus() => { isConnected: boolean; readyState: number; host: string; name: string; retryCount: number; }resetDBConnection
Manually resets retry count and attempts reconnection. Useful for recovering from Docker container restarts.
resetDBConnection() => Promise<void>logger
Winston logger instance with JSON formatting, timestamps, and error stack traces.
logger: winston.Logger;Development
# Install dependencies
pnpm install
# Build the package
pnpm run build
# Publish to npm
npm publishLicense
ISC
