const express = require('express');
const AppError = require('./AppError');
const asyncHandler = require('./asyncHandler');
const errorMiddleware = require('./errorMiddleware');
const UserRepo = require('./UserRepo');
const app = express();
app.use(express.json());
app.get('/users/:id', asyncHandler(async function (req, res) {
const user = await UserRepo.findById(req.params.id);
if (!user) throw AppError.notFound('User');
res.json(user);
}));
app.post('/users', asyncHandler(async function (req, res) {
if (!req.body.email) throw AppError.badRequest('email is required');
const user = await UserRepo.create(req.body);
res.status(201).json(user);
}));
app.use(function (req, res, next) {
next(AppError.notFound('Route'));
});
app.use(errorMiddleware);
module.exports = app;
class AppError extends Error {
constructor(message, statusCode, code) {
super(message);
this.statusCode = statusCode;
this.code = code || 'INTERNAL_ERROR';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
static badRequest(message) {
return new AppError(message || 'Invalid request', 400, 'BAD_REQUEST');
}
static unauthorized(message) {
return new AppError(message || 'Not authenticated', 401, 'UNAUTHORIZED');
}
static notFound(resource) {
const name = resource || 'Resource';
return new AppError(name + ' not found', 404, 'NOT_FOUND');
}
}
module.exports = AppError;
function asyncHandler(fn) {
return function (req, res, next) {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
module.exports = asyncHandler;
const AppError = require('./AppError');
function normalize(err) {
if (err instanceof AppError) return err;
if (err.name === 'ValidationError') {
const detail = Object.values(err.errors || {})
.map(function (e) { return e.message; })
.join(', ');
return AppError.badRequest(detail || 'Validation failed');
}
if (err.code === 11000) {
const field = Object.keys(err.keyValue || {})[0] || 'field';
return new AppError(field + ' already exists', 409, 'DUPLICATE');
}
return err;
}
function errorMiddleware(err, req, res, next) {
if (res.headersSent) return next(err);
const error = normalize(err);
const isOperational = error.isOperational === true;
const statusCode = error.statusCode || 500;
if (req.log) {
req.log.error({ err: error, path: req.path, code: error.code }, 'request failed');
}
const body = {
error: {
code: error.code || 'INTERNAL_ERROR',
message: isOperational ? error.message : 'Something went wrong'
}
};
if (process.env.NODE_ENV !== 'production') {
body.error.stack = error.stack;
}
res.status(statusCode).json(body);
}
module.exports = errorMiddleware;
This snippet shows the standard way to funnel every failure in an Express app through a single error-handling middleware so that responses share one JSON shape regardless of where the error originated. The core idea is to separate operational errors — expected conditions like a missing record or a bad payload — from programmer errors like a null dereference, and to let the middleware decide what leaks to the client.
In AppError.js, a small class extends the native Error so thrown values carry an HTTP statusCode, a machine-readable code, and an isOperational flag. The static helpers notFound, badRequest, and unauthorized act as factories so route code reads declaratively (throw AppError.notFound('User')) instead of hand-building status objects. Marking isOperational matters because the middleware trusts these messages, while treating everything else as an unexpected crash whose details should be hidden in production.
Express 4 does not catch rejected promises from async route handlers, so asyncHandler.js wraps a handler and pipes any rejection into next(). Without this, an awaited failure would hang the request instead of reaching the error middleware. This tiny wrapper is what makes throw usable inside async controllers.
errorMiddleware.js is the terminal handler, identified by its four-argument signature (err, req, res, next) that Express uses to distinguish error middleware. It normalizes known third-party errors first — a Mongoose ValidationError or a duplicate-key code 11000 — into an AppError with a sane status. It then derives statusCode and a payload, logs full details server-side via req.log, and only exposes message for operational errors; non-operational errors collapse to a generic message so stack traces and internal wording never reach clients. The stack field is attached solely outside production to aid debugging.
app.js wires it together: routes use asyncHandler, an explicit throw demonstrates a 404, a catch-all produces notFound for unmatched paths, and errorMiddleware is registered last so it sees everything. The key trade-off is centralization versus locality — controllers stay thin and consistent, at the cost of one place that must understand every error type it wants to translate. Registering the middleware after all routes and returning early on res.headersSent are the common pitfalls this layout avoids.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use tracing::{info, instrument};
#[instrument]
fn process_request(user_id: u64) {
info!(user_id, "Processing request");
// Work happens here
tracing for structured logging and distributed tracing
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
Share this code
Here's the card — post it anywhere.