M
Middleware
Type-safe Express middleware for authentication, validation, error handling, and logging.
Code
typescript
1import { Request, Response, NextFunction, ErrorRequestHandler } from "express";2import { z, ZodSchema } from "zod";3 4// Extend Express Request type5declare global {6 namespace Express {7 interface Request {8 user?: {9 id: string;10 email: string;11 role: "admin" | "user";12 };13 requestId?: string;14 }15 }16}17 18// Custom error class19class AppError extends Error {20 constructor(21 public statusCode: number,22 message: string,23 public isOperational = true24 ) {25 super(message);26 Object.setPrototypeOf(this, AppError.prototype);27 }28}29 30// Request ID middleware31const requestIdMiddleware = (32 req: Request,33 res: Response,34 next: NextFunction35): void => {36 req.requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2)}`;37 res.setHeader("X-Request-ID", req.requestId);38 next();39};40 41// Logger middleware42const loggerMiddleware = (43 req: Request,44 res: Response,45 next: NextFunction46): void => {47 const start = Date.now();48 49 res.on("finish", () => {50 const duration = Date.now() - start;51 console.log(52 `[${req.requestId}] ${req.method} ${req.path} ${res.statusCode} - ${duration}ms`53 );54 });55 56 next();57};58 59// Authentication middleware60const authMiddleware = (61 req: Request,62 res: Response,63 next: NextFunction64): void => {65 const token = req.headers.authorization?.replace("Bearer ", "");66 67 if (!token) {68 res.status(401).json({ error: "No token provided" });69 return;70 }71 72 try {73 // Verify token (simplified - use JWT in production)74 const decoded = JSON.parse(Buffer.from(token, "base64").toString());75 req.user = decoded;76 next();77 } catch {78 res.status(401).json({ error: "Invalid token" });79 }80};81 82// Role-based authorization middleware factory83const requireRole = (...roles: Array<"admin" | "user">) => {84 return (req: Request, res: Response, next: NextFunction): void => {85 if (!req.user) {86 res.status(401).json({ error: "Not authenticated" });87 return;88 }89 90 if (!roles.includes(req.user.role)) {91 res.status(403).json({ error: "Insufficient permissions" });92 return;93 }94 95 next();96 };97};98 99// Validation middleware factory using Zod100const validate = <T extends ZodSchema>(schema: T, source: "body" | "query" | "params" = "body") => {101 return (req: Request, res: Response, next: NextFunction): void => {102 const result = schema.safeParse(req[source]);103 104 if (!result.success) {105 res.status(400).json({106 error: "Validation failed",107 details: result.error.errors.map((e) => ({108 path: e.path.join("."),109 message: e.message,110 })),111 });112 return;113 }114 115 req[source] = result.data;116 next();117 };118};119 120// Rate limiting middleware121const rateLimit = (maxRequests: number, windowMs: number) => {122 const requests = new Map<string, { count: number; resetTime: number }>();123 124 return (req: Request, res: Response, next: NextFunction): void => {125 const ip = req.ip || "unknown";126 const now = Date.now();127 const record = requests.get(ip);128 129 if (!record || now > record.resetTime) {130 requests.set(ip, { count: 1, resetTime: now + windowMs });131 next();132 return;133 }134 135 if (record.count >= maxRequests) {136 res.status(429).json({137 error: "Too many requests",138 retryAfter: Math.ceil((record.resetTime - now) / 1000),139 });140 return;141 }142 143 record.count++;144 next();145 };146};147 148// Async handler wrapper149const asyncHandler = <T>(150 fn: (req: Request, res: Response, next: NextFunction) => Promise<T>151) => {152 return (req: Request, res: Response, next: NextFunction): void => {153 Promise.resolve(fn(req, res, next)).catch(next);154 };155};156 157// Global error handler158const errorHandler: ErrorRequestHandler = (err, req, res, next) => {159 console.error(`[${req.requestId}] Error:`, err);160 161 if (err instanceof AppError) {162 res.status(err.statusCode).json({163 error: err.message,164 ...(process.env.NODE_ENV === "development" && { stack: err.stack }),165 });166 return;167 }168 169 res.status(500).json({170 error: "Internal server error",171 ...(process.env.NODE_ENV === "development" && {172 message: err.message,173 stack: err.stack,174 }),175 });176};177 178// Usage example179const UserSchema = z.object({180 name: z.string().min(2),181 email: z.string().email(),182});183 184// app.use(requestIdMiddleware);185// app.use(loggerMiddleware);186// app.use(rateLimit(100, 60000));187// app.post("/users", authMiddleware, requireRole("admin"), validate(UserSchema), createUser);188// app.use(errorHandler);189 190export {191 AppError,192 requestIdMiddleware,193 loggerMiddleware,194 authMiddleware,195 requireRole,196 validate,197 rateLimit,198 asyncHandler,199 errorHandler,200};Run this example locally
$ npm install express zod @types/express