Lesson 23 +10 XP

Project: Full-Stack Express/TypeScript API Boilerplate

Project 3: Express / TypeScript REST API

Architect a production-ready Express API with generic Request handlers, middleware typing, and custom error handling.

Custom Request Interface & Error Handling

import { Request, Response, NextFunction } from "express";

export interface AuthenticatedRequest<Params = any, ResBody = any, ReqBody = any>
  extends Request<Params, ResBody, ReqBody> {
  user?: {
    id: string;
    role: "admin" | "user";
  };
}

export class AppError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    Object.setPrototypeOf(this, new.target.prototype);
  }
}

Async Handler Wrapper & Controller

type AsyncController<Req extends Request = Request> = (
  req: Req,
  res: Response,
  next: NextFunction
) => Promise<void | Response>;

export const asyncHandler = (fn: AsyncController) => {
  return (req: Request, res: Response, next: NextFunction) => {
    fn(req, res, next).catch(next);
  };
};

// Controller logic
interface CreateUserDTO {
  username: string;
  email: string;
}

export const createUser = asyncHandler(
  async (req: AuthenticatedRequest<{}, {}, CreateUserDTO>, res: Response) => {
    const { username, email } = req.body;
    if (!username || !email) {
      throw new AppError(400, "Username and email required");
    }
    return res.status(201).json({ status: "success", data: { username, email } });
  }
);

TL;DR

  • Extends standard Express Request interfaces with custom payload/user properties.
  • Wraps async controllers to automatically forward promise rejections to Express error middleware.
  • Enforces strict DTO types on request bodies.