typescriptintermediate

Express Error Handling Middleware

Centralized error handling in Express with custom error classes, async wrapper, and structured responses.

typescript
import type { Request, Response, NextFunction, RequestHandler } from 'express';

// Custom error classes
class AppError extends Error {
  constructor(
    public statusCode: number,
    message: string,
    public code?: string,
  ) {
    super(message);
    this.name = 'AppError';
  }
}

class NotFoundError extends AppError {
  constructor(resource: string, id?: string) {
    super(404, id ? `${resource} '${id}' not found` : `${resource} not found`, 'NOT_FOUND');
  }
}

class ValidationError extends AppError {
  constructor(public errors: { field: string; message: string }[]) {
    super(400, 'Validation failed', 'VALIDATION_ERROR');
  }
}

class UnauthorizedError extends AppError {
  constructor(message = 'Unauthorized') {
    super(401, message, 'UNAUTHORIZED');
  }
}

// Async handler wrapper
const asyncHandler = (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>): RequestHandler =>
  (req, res, next) => fn(req, res, next).catch(next);

// Error handler middleware (must be last)
function errorHandler(err: Error, req: Request, res: Response, _next: NextFunction) {
  console.error(`[${req.method}] ${req.path}:`, err.message);

  if (err instanceof ValidationError) {
    return res.status(400).json({
      error: { code: err.code, message: err.message, details: err.errors },
    });
  }

  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      error: { code: err.code, message: err.message },
    });
  }

  // Unexpected errors — don't leak internals
  res.status(500).json({
    error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' },
  });
}

// Usage in routes
// app.get('/users/:id', asyncHandler(async (req, res) => {
//   const user = await db.findUser(req.params.id);
//   if (!user) throw new NotFoundError('User', req.params.id);
//   res.json(user);
// }));
// app.use(errorHandler);

export { AppError, NotFoundError, ValidationError, UnauthorizedError, asyncHandler, errorHandler };

Sponsored

Railway

Use Cases

  • API error standardization
  • Async route error handling
  • Structured error responses

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.