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 type
5declare 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 class
19class AppError extends Error {
20 constructor(
21 public statusCode: number,
22 message: string,
23 public isOperational = true
24 ) {
25 super(message);
26 Object.setPrototypeOf(this, AppError.prototype);
27 }
28}
29
30// Request ID middleware
31const requestIdMiddleware = (
32 req: Request,
33 res: Response,
34 next: NextFunction
35): 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 middleware
42const loggerMiddleware = (
43 req: Request,
44 res: Response,
45 next: NextFunction
46): 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 middleware
60const authMiddleware = (
61 req: Request,
62 res: Response,
63 next: NextFunction
64): 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 factory
83const 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 Zod
100const 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 middleware
121const 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 wrapper
149const 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 handler
158const 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 example
179const 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