Careeroza
Typed Middleware and Error Handling
Middleware and Augmented Request
import type { NextFunction, Request, Response, ErrorRequestHandler, RequestHandler } from "express";
// types/express.d.ts adds: interface Request { user?: { id: string; role: "admin" | "customer" } }
export const requireAuth: RequestHandler = (req, res, next) => {
const token = req.header("authorization")?.replace("Bearer ", "");
if (!token) return res.status(401).json({ message: "Login required" });
req.user = { id: "u_1", role: token === "admin-token" ? "admin" : "customer" };
next();
};
export const requireRole = (role: "admin"): RequestHandler => (req, res, next) =>
req.user?.role === role ? next() : res.status(403).json({ message: "Forbidden" });
export class HttpError extends Error {
constructor(public status: number, message: string) { super(message); }
}
// async errors: wrap handlers so rejected promises reach the error handler (Express 5 does this automatically)
export const asyncHandler =
<P, ResB, ReqB>(fn: (req: Request<P, ResB, ReqB>, res: Response<ResB>, next: NextFunction) => Promise<unknown>) =>
(req: Request<P, ResB, ReqB>, res: Response<ResB>, next: NextFunction) => { fn(req, res, next).catch(next); };
export const errorHandler: ErrorRequestHandler = (err: unknown, _req, res, _next) => {
const status = err instanceof HttpError ? err.status : 500;
const message = err instanceof HttpError ? err.message : "Internal server error";
res.status(status).json({ message });
};The Middleware Idea, Runnable
Example
Express middleware is a chain of functions that each call next(). This typed simulation shows how auth, role check and the handler run in order, and how a thrown HttpError becomes a response.
type Req = { path: string; headers: Record<string, string>; user?: { id: string; role: "admin" | "customer" } };
type Res = { status: number; body: unknown };
type Middleware = (req: Req, res: Res, next: () => void) => void;
class HttpError extends Error { constructor(public status: number, message: string) { super(message); } }
const auth: Middleware = (req, _res, next) => {
const token = req.headers.authorization;
if (!token) throw new HttpError(401, "Login required");
req.user = { id: "u_1", role: token === "admin-token" ? "admin" : "customer" };
next();
};
const adminOnly: Middleware = (req, _res, next) => {
if (req.user?.role !== "admin") throw new HttpError(403, "Forbidden");
next();
};
const handler: Middleware = (req, res) => { res.status = 200; res.body = { report: "sales", by: req.user?.id }; };
function run(chain: Middleware[], req: Req): Res {
const res: Res = { status: 404, body: null };
const step = (i: number): void => { chain[i]?.(req, res, () => step(i + 1)); };
try { step(0); } catch (e) {
res.status = e instanceof HttpError ? e.status : 500;
res.body = { message: e instanceof Error ? e.message : "error" };
}
return res;
}
const chain = [auth, adminOnly, handler];
for (const token of [undefined, "user-token", "admin-token"]) {
const r = run(chain, { path: "/admin/reports", headers: token ? { authorization: token } : {} });
console.log(String(token).padEnd(11), r.status, JSON.stringify(r.body));
}Output
undefined 401 {"message":"Login required"}
user-token 403 {"message":"Forbidden"}
admin-token 200 {"report":"sales","by":"u_1"}