javascript 105 lines · 4 tabs

Centralized Express Error Handling with Typed AppError and Async Wrapper

Shared by codesnips Aug 2026
4 tabs
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;
4 files · javascript Explain with highlit

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

typescript
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

typescript reliability retry
by codesnips 2 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
rust
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

rust observability tracing
by Marcus Chen 1 tab
typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
typescript
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)

security node jwt
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Centralized Express Error Handling with Typed AppError and Async Wrapper — share card
Link copied