Middleware
Checking access...
Middleware functions are the building blocks of Express applications. They have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle.
What is Middleware?
Each middleware function can:
- Execute any code
- Modify the
reqandresobjects - End the request-response cycle
- Call the next middleware in the stack
const logger = (req, res, next) => { console.log(`${req.method} ${req.path} — ${new Date().toISOString()}`); next(); // pass control to the next middleware};
app.use(logger); // applies to all routesMiddleware Flow
Request → Middleware 1 → Middleware 2 → Route Handler → Response ↓ ↓ next() next()If a middleware doesn’t call next() and doesn’t send a response, the request hangs.
Built-in Middleware
express.json()
Parses incoming JSON request bodies:
app.use(express.json());
app.post("/api/users", (req, res) => { console.log(req.body); // parsed JSON object res.json({ received: req.body });});express.urlencoded()
Parses form data from application/x-www-form-urlencoded:
app.use(express.urlencoded({ extended: true }));// extended: true allows rich objects (not just strings)express.static()
Serves static files from a directory:
// Serve files from the "public" folderapp.use(express.static("public"));
// Now http://localhost:3000/image.jpg serves public/image.jpg
// With virtual path prefixapp.use("/static", express.static("public"));// http://localhost:3000/static/image.jpg
// Multiple directories (searched in order)app.use(express.static("public"));app.use(express.static("uploads"));Third-Party Middleware
cors
Enable Cross-Origin Resource Sharing:
npm install corsconst cors = require("cors");
// Allow all origins (development)app.use(cors());
// Specific configurationapp.use(cors({ origin: "https://myapp.com", methods: ["GET", "POST"], allowedHeaders: ["Content-Type", "Authorization"], credentials: true, maxAge: 86400, // cache preflight for 24 hours}));morgan (HTTP request logger)
npm install morganconst morgan = require("morgan");
// Predefined formatsapp.use(morgan("dev")); // concise output colored by statusapp.use(morgan("combined")); // Apache-style (more verbose)app.use(morgan("tiny")); // minimal
// Custom formatapp.use(morgan(":method :url :status :response-time ms"));helmet (Security headers)
npm install helmetconst helmet = require("helmet");
app.use(helmet()); // sets various HTTP security headersCustom Middleware
Request Logger
const requestLogger = (req, res, next) => { const start = Date.now();
res.on("finish", () => { const duration = Date.now() - start; console.log( `${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms` ); });
next();};
app.use(requestLogger);Authentication Check
const requireAuth = (req, res, next) => { const token = req.headers.authorization;
if (!token) { return res.status(401).json({ error: "Authentication required" }); }
try { const user = jwt.verify(token.replace("Bearer ", ""), process.env.JWT_SECRET); req.user = user; // Attach user to request for downstream handlers next(); } catch (err) { return res.status(401).json({ error: "Invalid token" }); }};
// Apply to specific routesapp.get("/api/profile", requireAuth, (req, res) => { res.json({ user: req.user });});Rate Limiting
const rateLimit = {};
const rateLimiter = (req, res, next) => { const ip = req.ip; const now = Date.now(); const windowMs = 60 * 1000; // 1 minute const maxRequests = 100;
if (!rateLimit[ip]) { rateLimit[ip] = { count: 1, start: now }; return next(); }
if (now - rateLimit[ip].start > windowMs) { rateLimit[ip] = { count: 1, start: now }; return next(); }
rateLimit[ip].count++;
if (rateLimit[ip].count > maxRequests) { return res.status(429).json({ error: "Too many requests" }); }
next();};
app.use(rateLimiter);For production, use express-rate-limit package instead.
Route-Specific Middleware
// Middleware applied to specific routeconst validateUserInput = (req, res, next) => { const { name, email } = req.body;
if (!name || name.length < 2) { return res.status(400).json({ error: "Name must be at least 2 characters" }); }
if (!email || !email.includes("@")) { return res.status(400).json({ error: "Valid email is required" }); }
next();};
app.post("/api/users", validateUserInput, createUser);Multiple Middleware Functions
app.get( "/api/protected", requireAuth, // first: check auth validateInput, // second: validate data cacheCheck, // third: check cache (req, res) => { // fourth: route handler res.json({ data: "protected data" }); });Error-Handling Middleware
Error-handling middleware has four parameters instead of three:
// This must be defined AFTER all routes and other middlewareapp.use((err, req, res, next) => { console.error("Error:", err);
res.status(err.status || 500).json({ error: { message: err.message || "Internal server error", ...(process.env.NODE_ENV === "development" && { stack: err.stack }), }, });});Custom Error Class
class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); }}
// Routes can throw operational errors:app.get("/users/:id", async (req, res, next) => { try { const user = await findUser(req.params.id); if (!user) { throw new AppError("User not found", 404); } res.json(user); } catch (err) { next(err); // passes to error handler }});
// Global error handlerapp.use((err, req, res, next) => { const statusCode = err.statusCode || 500;
res.status(statusCode).json({ status: "error", statusCode, message: err.isOperational ? err.message : "Something went wrong", });});Middleware Ordering
Order matters — middleware runs in the order it’s defined:
// 1. Security & logging (applied first)app.use(helmet());app.use(cors());app.use(morgan("dev"));
// 2. Body parsersapp.use(express.json());app.use(express.urlencoded({ extended: true }));
// 3. Static filesapp.use(express.static("public"));
// 4. Custom middlewareapp.use(requestLogger);
// 5. Routesapp.use("/api/users", usersRouter);app.use("/api/products", productsRouter);
// 6. 404 handler (must be after all routes)app.use("*", (req, res) => { res.status(404).json({ error: "Route not found" });});
// 7. Error handler (must be last)app.use((err, req, res, next) => { console.error(err); res.status(500).json({ error: "Internal server error" });});Quick Reference
// Application-levelapp.use(middleware); // all routesapp.use("/path", middleware); // specific path prefix
// Router-levelrouter.use(middleware);
// Route-levelapp.get("/path", mw1, mw2, handler);
// Error-handling (4 params)app.use((err, req, res, next) => {});
// Common middlewareexpress.json() // parse JSON bodiesexpress.urlencoded() // parse form bodiesexpress.static("dir") // serve static filescors() // enable CORShelmet() // security headersmorgan("dev") // request loggingPractice Exercises
Request timer middleware: Create middleware that logs
[METHOD] /path — Nmsfor every request usingprocess.hrtime()for precise timing.API key authentication: Build middleware that checks for an
x-api-keyheader. If the key matches a stored value (in env), allow the request. Otherwise, return 401. Protect specific routes with this middleware.Error handling system: Create a custom
AppErrorclass with status code and anisOperationalflag. Build an error handler middleware that distinguishes operational errors (return the message) from programming errors (return generic message, log the full error).