Express & Middleware

15 questions found

What is Express.js, and what problem does it solve compared to using Node.js's raw http module directly?

Beginner
Express is a minimal, unopinionated web framework built on top of Node's core http module, providing routing (mapping URLs and HTTP methods to handler functions), middleware composition, request/response helper methods (like res.json(), req.params), and template-engine integration -- all of which would otherwise need to be implemented manually and repetitively using the lower-level, more verbose raw http module.
// Raw http module: verbose, manual routing
const http = require('node:http');
http.createServer((req, res) => {
  if (req.url === '/users' && req.method === 'GET') { res.end(JSON.stringify(users)); }
}).listen(3000);

// Express: concise, declarative routing
const express = require('express');
const app = express();
app.get('/users', (req, res) => res.json(users));
app.listen(3000);
Real-world example A team building a REST API chooses Express over the raw http module specifically for its concise routing syntax and rich middleware ecosystem (body parsing, CORS, authentication), avoiding having to hand-roll all of that boilerplate themselves.

Common follow-ups: In what scenario might a team deliberately avoid Express and use the raw http module directly instead?;How does Express compare to newer, similarly-purposed frameworks like Fastify or Koa?

HTTP & HTTPS Modules;RESTful API Design with Express

What is middleware in Express, and what is the significance of the order in which middleware is registered?

Intermediate
Middleware functions receive the request, response, and a 'next' callback, executed in the exact order they're registered via app.use() or as route-specific handlers -- order matters critically because each middleware can modify the request/response objects or short-circuit the chain entirely before it reaches later middleware or the final route handler, meaning something like an authentication check must be registered before any route it's meant to protect.
app.use(express.json()); // must come before routes that read req.body
app.use(loggingMiddleware); // logs every request
app.use('/admin', requireAuth); // only applies to routes starting with /admin
app.get('/admin/dashboard', (req, res) => res.json(dashboardData)); // protected by the above
Real-world example A team accidentally registered their authentication middleware after their protected routes instead of before, meaning every request reached the protected route handler without ever being checked, illustrating exactly why middleware order is a common and consequential mistake.

Common follow-ups: What happens if a middleware function never calls next() and never sends a response?;How does route-specific middleware (passed as an extra argument to app.get()) differ from application-level middleware registered via app.use()?

Architecture & Design Patterns;Security

How do you write custom Express middleware that needs to perform asynchronous work, like checking a value against a database, before calling next()?

Advanced
An async middleware function performs its await-based work and then calls next() to continue the chain, or next(err) to forward an error -- because Express (pre-v5) doesn't automatically catch a rejected promise from an async middleware, any error thrown inside it needs to be explicitly caught and passed to next(err), typically using the same asyncHandler wrapper pattern used for async route handlers.
const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);

const checkRateLimit = asyncHandler(async (req, res, next) => {
  const requestCount = await redis.incr(`rate:${req.ip}`);
  if (requestCount > 100) return res.status(429).json({ error: 'Too many requests' });
  next();
});

app.use(checkRateLimit);
Real-world example A rate-limiting middleware that queries Redis asynchronously before deciding whether to allow a request through is wrapped in an asyncHandler utility, ensuring that if the Redis call itself fails, the error is properly forwarded to Express's error-handling middleware rather than crashing the process.

Common follow-ups: What happens specifically if this async middleware throws without being wrapped in asyncHandler, in an Express 4 application?;How does this pattern differ once Express 5's native async error handling makes the wrapper largely unnecessary?

Async Patterns;Error Handling

What is the difference between application-level, router-level, and route-specific middleware in Express?

Intermediate
Application-level middleware (app.use()) applies to every request reaching the app (or matching a specified path prefix). Router-level middleware works the same way but is scoped to a specific express.Router() instance, letting a group of related routes (like everything under /api/v1/users) share middleware without affecting the rest of the app. Route-specific middleware is passed as an additional argument directly to a specific route definition, applying only to that single route.
// Application-level: every request
app.use(loggingMiddleware);

// Router-level: scoped to this router's routes only
const userRouter = express.Router();
userRouter.use(requireAuth);
userRouter.get('/profile', getProfileHandler);
app.use('/users', userRouter);

// Route-specific: applies only to this one route
app.post('/upload', uploadMiddleware, uploadHandler);
Real-world example An API structures its authentication requirement at the router level for an entire /admin router (protecting every admin route automatically), while applying a specific file-size-limiting middleware only to the single /upload route that actually needs it.

Common follow-ups: How does mounting a router with app.use('/prefix', router) affect the paths defined within that router?;What's the benefit of organizing related routes into separate router modules rather than defining everything directly on the main app object?

Architecture & Design Patterns;RESTful API Design with Express

How would you implement request validation middleware in Express using a schema-validation library like Zod or Joi?

Advanced
Validation middleware defines an expected schema for the request body, query parameters, or route parameters, validates the incoming request against it before the route handler runs, and either calls next() if valid or responds with a 400 error detailing exactly what validation failed -- centralizing validation logic outside individual route handlers and ensuring route handlers can trust the shape of the data they receive.
const { z } = require('zod');

const createUserSchema = z.object({ email: z.string().email(), age: z.number().min(18) });

function validate(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) return res.status(400).json({ errors: result.error.issues });
    req.body = result.data;
    next();
  };
}

app.post('/users', validate(createUserSchema), createUserHandler);
Real-world example An API extracts request validation into a reusable Zod-schema-based middleware, letting every route handler assume incoming data already matches the expected shape, eliminating scattered manual validation checks (and their associated bugs) throughout the route handler code itself.

Common follow-ups: What's the benefit of validating and transforming data in a single middleware step rather than validating inline within each handler?;How would you handle validating nested or deeply structured request bodies with this same pattern?

Security;Error Handling

What does the express.json() and express.urlencoded() built-in middleware do, and why must it be registered before routes that need to read req.body?

Intermediate
express.json() parses an incoming request body with a JSON content type into a JavaScript object, populating req.body; express.urlencoded() does the same for traditional HTML form submissions (application/x-www-form-urlencoded content type) -- both must be registered before any route that reads req.body, since Express processes middleware in registration order, and without this parsing step, req.body would remain undefined for any route handler that relies on it.
app.use(express.json()); // parses JSON bodies
app.use(express.urlencoded({ extended: true })); // parses form submissions

app.post('/users', (req, res) => {
  console.log(req.body); // only populated because the middleware above ran first
});
Real-world example A team debugging why req.body was always undefined in their POST route handlers discovers they'd forgotten to register express.json() at all, meaning Express never actually parsed the incoming JSON request body before it reached their route handler.

Common follow-ups: What happens if a client sends a JSON body but express.json() isn't registered -- does the request fail, or does req.body simply stay undefined?;What does the 'extended' option on express.urlencoded() actually control?

RESTful API Design with Express;HTTP & HTTPS Modules

How would you implement a request-logging middleware in Express that captures method, path, status code, and response time for every request?

Advanced
A logging middleware records the request's start time when it first runs, then attaches a listener to the response's 'finish' event (fired once the response has been fully sent) to calculate the elapsed duration and log the complete request/response summary -- this pattern, rather than logging synchronously right after calling next(), is necessary because next() returns before the response is actually sent, so the status code and true total duration aren't known until the response actually finishes.
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
  });
  next();
});
Real-world example An API's custom logging middleware records every request's method, path, final status code, and total response time by listening for the response's 'finish' event, giving operations visibility into which endpoints are slow or frequently erroring without needing a third-party logging library.

Common follow-ups: Why does logging immediately after calling next() give an incorrect status code and duration compared to listening for 'finish'?;How does this hand-rolled approach compare to using a mature middleware like morgan for the same purpose?

Logging & Monitoring;Performance Optimization & Profiling

What is the purpose of the helmet middleware package for an Express application, and what specific security headers does it set?

Intermediate
helmet sets several HTTP response headers that provide baseline protection against common web vulnerabilities with minimal configuration -- including Content-Security-Policy (restricting what resources a page can load, mitigating XSS), X-Frame-Options (preventing clickjacking by controlling whether the page can be embedded in an iframe), Strict-Transport-Security (forcing browsers to only connect via HTTPS), and X-Content-Type-Options (preventing MIME-type sniffing attacks) -- collectively raising an Express app's baseline security posture with a single line of middleware.
const helmet = require('helmet');
app.use(helmet()); // applies a sensible set of security headers by default

// Customizing a specific header's behavior
app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"] } } }));
Real-world example A security review of a production Express API recommends adding helmet as one of the first, easiest improvements, immediately gaining protection against clickjacking, MIME-sniffing, and several other common attack vectors with a single line of middleware configuration.

Common follow-ups: Which of helmet's default headers might need customization for an API serving embeddable widgets meant to be placed in an iframe?;How does Content-Security-Policy specifically help mitigate XSS attacks?

Security;HTTP & HTTPS Modules

How would you structure a large Express application using the Router to organize routes into feature-based or resource-based modules?

Advanced
Rather than defining every route directly on the main app object in one large file, express.Router() lets related routes be grouped into their own module (users.js, orders.js, each exporting a configured Router instance), which the main application file then mounts at an appropriate base path -- this keeps each feature's routing, middleware, and validation logic self-contained and makes a growing application's route structure much easier to navigate and maintain.
// routes/users.js
const router = express.Router();
router.get('/', listUsers);
router.get('/:id', getUser);
router.post('/', createUser);
module.exports = router;

// app.js
app.use('/api/users', require('./routes/users'));
app.use('/api/orders', require('./routes/orders'));
Real-world example A growing e-commerce API splits its routes into separate users.js, orders.js, and products.js router modules, each mounted at its own base path in the main app file, replacing what had become an unwieldy 2,000-line single routes file with a clean, feature-organized structure.

Common follow-ups: How would you further organize a router module to separate route definitions from their actual handler logic (controllers)?;What's the benefit of this modular structure specifically for a team where multiple developers work on different features simultaneously?

Architecture & Design Patterns;Git & Project Management

What is CORS middleware in Express, and how does the cors package simplify configuring Cross-Origin Resource Sharing?

Intermediate
CORS is a browser security mechanism that blocks a web page from making requests to a different origin (domain, protocol, or port) than the one it was served from, unless the target server explicitly allows it via specific response headers -- the cors npm package for Express handles setting these headers (Access-Control-Allow-Origin, Access-Control-Allow-Methods, and others) and correctly responding to the browser's preflight OPTIONS requests, based on a simple configuration object rather than needing to set each header manually.
const cors = require('cors');

app.use(cors({
  origin: 'https://myapp.com', // only this origin can call this API from a browser
  methods: ['GET', 'POST'],
  credentials: true,
}));
Real-world example A public API serving a separately-hosted frontend at a different domain configures cors() with that specific frontend's origin allowed, letting the browser-based frontend successfully make requests that would otherwise be blocked by the browser's same-origin policy.

Common follow-ups: Why does a browser send a preflight OPTIONS request before certain cross-origin requests, and how does the cors middleware handle it?;What's the security risk of configuring CORS with a wildcard '*' origin combined with credentials: true?

Security;HTTP & HTTPS Modules

Showing 1–10 of 15