🚀

Top 49 Express.js Interview Questions & Answers (2026)

49 Questions 20 Beginner 18 Intermediate 11 Advanced

About Express.js

Top 50 Express.js interview questions covering routing, middleware, REST APIs, authentication, error handling, and production best practices. Companies hiring for Express.js roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.

What to Expect in a Express.js Interview

Expect a mix of conceptual and practical Express.js questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.

How to Use This Guide

Work through the Express.js questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.

Curated by Tech Baithak Editorial Team  ·  Last updated: June 2026

Beginner 20 questions

Core concepts every Express.js developer must know.

01

What is Express.js?

Express.js is a minimal, unopinionated web application framework for Node.js. It provides a thin layer of fundamental web application features — routing, middleware, request/response handling — without obscuring the underlying Node.js HTTP module. It is the most popular Node.js framework and forms the foundation for many other frameworks like Nest.js and Sails.js. Express makes it easy to build RESTful APIs, web servers, and microservices with very little boilerplate. Its minimalist design means you choose your own tools for databases, authentication, templating, and validation, giving you maximum flexibility.

Open this question on its own page
02

How do you create a basic Express.js server?

Install Express with npm install express, then create a server in a few lines: const express = require('express'); const app = express(); app.get('/', (req, res) => res.send('Hello World')); app.listen(3000);. express() creates an application instance. app.get() registers a route handler for HTTP GET requests. The handler receives a req (request) and res (response) object. res.send() sends a response. app.listen(port) starts the HTTP server. This is all you need for a minimal web server. In modern Node.js projects you can also use ES modules with import express from 'express' if your package.json has "type": "module".

Open this question on its own page
03

What is middleware in Express.js?

Middleware in Express are functions that have access to the request (req), response (res), and a next function. They sit in the middleware pipeline between the incoming request and the final route handler. Middleware can execute code, modify req/res, end the request-response cycle, or call next() to pass control to the next middleware. Examples: logging every request, parsing JSON bodies, verifying authentication tokens, adding CORS headers. Register middleware with app.use(). Middleware functions run in the order they are defined, so ordering matters — for example, authentication middleware should run before protected route handlers.

Open this question on its own page
04

What is the difference between app.use() and app.get() in Express?

app.use() registers middleware that runs for all HTTP methods matching the specified path (or all paths if no path is given). It is used for general-purpose middleware like body parsers, loggers, and authentication. If a path is specified, it matches any request whose URL starts with that path. app.get() registers a route handler for HTTP GET requests only and requires an exact path match (or parameterized match). Similarly, app.post(), app.put(), app.delete(), and app.patch() handle their respective HTTP methods. The key rule: app.use() for cross-cutting concerns, app.METHOD() for specific API endpoints.

Open this question on its own page
05

What are route parameters in Express.js?

Route parameters are named URL segments that capture values from the URL path, defined with a colon prefix. For example, app.get('/users/:id', (req, res) => { res.send(req.params.id); }) — a request to /users/42 sets req.params.id to "42". Multiple parameters: /users/:userId/posts/:postId. All parameter values are strings — parse them with parseInt() or validate/cast them in middleware. Route parameters differ from query strings (/users?id=42, accessed via req.query.id) — parameters are part of the route path and typically identify a specific resource, while query strings are optional filters.

Open this question on its own page
06

How do you parse request bodies in Express.js?

Express does not parse request bodies by default. For JSON bodies (from REST API clients), use the built-in middleware: app.use(express.json()). This parses incoming requests with Content-Type application/json and populates req.body. For URL-encoded form data (from HTML forms): app.use(express.urlencoded({ extended: true })). The extended: true option uses the qs library for rich objects; false uses the basic querystring module. Both middlewares must be registered before the route handlers that need req.body. For file uploads, use a dedicated package like multer.

Open this question on its own page
07

What is the Express.js Router?

The Express Router is a mini-application that handles routing logic, allowing you to modularize your route definitions into separate files. Create a router: const router = express.Router();. Define routes on it: router.get('/', handler); router.post('/', handler);. Then mount it in the main app: app.use('/users', router). A GET to /users/ now matches router.get('/'). This is essential for organizing large applications — you create separate router files for each resource (routes/users.js, routes/posts.js) and mount them all in app.js. Routers can also have their own middleware, creating middleware scoped to specific route groups.

Open this question on its own page
08

How do you send different types of responses in Express.js?

Express provides several response methods on res: res.send() sends a generic response (string, Buffer, or object). res.json(obj) sends a JSON response and automatically sets the Content-Type: application/json header. res.status(code) sets the HTTP status code and is typically chained: res.status(404).json({ message: 'Not found' }). res.sendFile(path) sends a file as the response. res.redirect(url) sends a 302 redirect. res.render(view, data) renders a template (requires a template engine). res.end() sends an empty response. Always call exactly one response method per request — calling multiple will throw an error.

Open this question on its own page
09

What is the next() function in Express middleware?

The next() function is the third parameter of every Express middleware function. Calling next() passes control to the next middleware in the stack. If you do not call next() and do not send a response, the request will hang. Calling next(err) with an argument skips all remaining regular middleware and jumps to the error-handling middleware (which has the signature (err, req, res, next)). This is the standard pattern for propagating errors. Calling next('route') skips remaining handlers for the current route and passes control to the next matching route.

Open this question on its own page
10

How does error handling work in Express.js?

Express has a special error-handling middleware with four parameters: (err, req, res, next). It must be defined with exactly four parameters for Express to recognize it as an error handler. It is placed after all other middleware and routes. To trigger it, call next(err) from any middleware or route handler. Example: app.use((err, req, res, next) => { console.error(err); res.status(500).json({ error: err.message }); });. For async route handlers, errors thrown in async functions must be caught and passed to next(err) — Express does not automatically catch async errors in versions before 5.x. A popular solution is a asyncHandler wrapper that auto-catches and forwards async errors.

Open this question on its own page
11

What is CORS and how do you enable it in Express?

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that blocks web pages from making requests to a different domain than the one that served the page. When your React frontend on localhost:3000 calls your Express API on localhost:5000, the browser blocks it unless the server includes the appropriate CORS headers. In Express, install and use the cors package: const cors = require('cors'); app.use(cors()) — this allows all origins. For production, configure specific origins: app.use(cors({ origin: 'https://myapp.com' })). You can also allow specific methods, headers, and credentials. CORS headers only apply to browser clients — tools like Postman and curl are unaffected.

Open this question on its own page
12

What are query parameters in Express.js?

Query parameters are key-value pairs appended to the URL after a ?: /products?category=electronics&sort=price&page=2. Access them in Express via req.query: req.query.category returns "electronics", req.query.page returns "2" (always a string). Multiple values for the same key come through as an array: /filter?color=red&color=bluereq.query.color = ["red", "blue"]. Always validate and sanitize query parameters before use — never trust user input. Parse numbers with parseInt(), validate enums against allowed values, and use libraries like Joi or Zod for comprehensive validation schemas.

Open this question on its own page
13

How do you serve static files in Express.js?

Use the built-in express.static() middleware to serve static files (HTML, CSS, JavaScript, images) from a directory. app.use(express.static('public')) serves files from the public folder — a file at public/images/logo.png is accessible at /images/logo.png. You can mount it at a URL prefix: app.use('/static', express.static('public')). Use path.join(__dirname, 'public') for a reliable absolute path. The middleware sets appropriate Cache-Control and ETag headers automatically for browser caching. In production, serving static files through a CDN or Nginx is preferred over Express for better performance.

Open this question on its own page
14

What is the difference between req.params, req.query, and req.body?

These three objects on the Express request contain data from different parts of the HTTP request. req.params: values from URL path segments defined as route parameters (:id in /users/:id). Always strings. req.query: values from the URL query string (?key=value). Always strings (or arrays for repeated keys). Available without middleware. req.body: data from the request body (JSON, form data). Only populated after body-parsing middleware runs (express.json() or express.urlencoded()). Can be any parsed type. In a request to POST /users/42?verbose=true with body {"name":"Alice"}: req.params.id = "42", req.query.verbose = "true", req.body.name = "Alice".

Open this question on its own page
15

What HTTP status codes should RESTful APIs use?

RESTful APIs should use appropriate HTTP status codes to communicate the result of each request. 200 OK: successful GET, PUT, PATCH. 201 Created: successful POST that created a resource. 204 No Content: successful DELETE (no body returned). 400 Bad Request: invalid request data (validation failed). 401 Unauthorized: authentication required or invalid credentials. 403 Forbidden: authenticated but not authorized to access the resource. 404 Not Found: resource does not exist. 409 Conflict: duplicate data (e.g., email already taken). 422 Unprocessable Entity: semantically invalid data. 500 Internal Server Error: unexpected server-side error. Using the correct codes lets API consumers handle errors programmatically without parsing error messages.

Open this question on its own page
16

What is express.json() middleware?

express.json() is a built-in Express middleware that parses incoming requests with Content-Type: application/json and populates req.body with the parsed JavaScript object. It is the standard way to accept JSON data from API clients. Register it globally with app.use(express.json()) before your route definitions. If the JSON is malformed, the middleware automatically responds with a 400 error. You can configure options like limit to restrict request body size: express.json({ limit: '1mb' }). Before Express 4.16, this functionality required a separate body-parser package — it is now built in.

Open this question on its own page
17

What is a REST API?

A REST API (Representational State Transfer) is an architectural style for building web services that communicate over HTTP. Key constraints: Stateless — each request contains all the information needed; the server stores no session state between requests. Resource-based — endpoints represent resources (nouns): /users, /products/42. HTTP methods as verbs: GET (read), POST (create), PUT/PATCH (update), DELETE (delete). Uniform interface — consistent resource naming and standard response formats (usually JSON). Cacheable — responses should indicate if they can be cached. Express is ideal for REST APIs because its routing maps directly to REST resource/method combinations: router.get('/', list); router.post('/', create); router.get('/:id', getOne);.

Open this question on its own page
18

How do you set response headers in Express.js?

Set individual headers with res.set('Header-Name', 'value') or res.setHeader(). Set multiple headers at once: res.set({ 'Content-Type': 'application/json', 'X-Custom': 'value' }). Use res.type('json') as a shorthand for Content-Type. Read headers from the response with res.get('Header-Name'). For security, set headers like X-Content-Type-Options, X-Frame-Options, and Content-Security-Policy — or use the helmet package which sets all important security headers automatically. Note that headers must be set before calling res.send() or res.json() — they cannot be set after the response is sent.

Open this question on its own page
19

What is the difference between app.listen() and http.createServer()?

app.listen(port) is a convenience method in Express that internally calls http.createServer(app).listen(port) — it creates an HTTP server with the Express app as the request handler and starts listening. It is the simplest way to start an Express server. http.createServer(app) is the explicit Node.js approach. You use it directly when you need access to the raw server instance — for example to set up WebSocket support (socket.io attaches to the raw HTTP server), to create both HTTP and HTTPS servers from the same app, or to configure server-level options. const server = http.createServer(app); server.listen(3000); io.attach(server); is the standard pattern for Express + Socket.io.

Open this question on its own page
20

What is Nodemon and why is it useful with Express?

Nodemon is a development tool that monitors your Node.js project files for changes and automatically restarts the server when a change is detected. Without it, you must manually stop and restart the server after every code change. Install as a dev dependency: npm install --save-dev nodemon. Run your app: npx nodemon app.js or define a script in package.json: "dev": "nodemon app.js". Configure it via a nodemon.json file to ignore specific files or watch additional extensions. In production, Nodemon is not used — use a process manager like PM2 for restart-on-crash, clustering, and monitoring.

Open this question on its own page
Intermediate 18 questions

Practical knowledge for developers with hands-on experience.

01

How do you implement JWT authentication in Express.js?

JWT (JSON Web Token) authentication in Express involves two steps. Login: verify credentials, then sign a token: const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' }); send it in the response. Protected routes: create middleware that extracts the token from the Authorization: Bearer <token> header, verifies it: jwt.verify(token, process.env.JWT_SECRET), and attaches the decoded payload to req.user. If verification fails, respond with 401. Apply this middleware to protected routes: router.get('/profile', authMiddleware, profileHandler). Use the jsonwebtoken package. Store the JWT secret securely in environment variables, never hardcoded.

Open this question on its own page
02

What is Express middleware chaining and how does it work?

Middleware chaining allows you to run multiple middleware functions in sequence for a single route. You can chain them as multiple arguments: app.get('/admin', authenticate, authorize('admin'), handler). Or as an array: app.get('/admin', [authenticate, authorize('admin')], handler). Express calls them in order, and each must call next() to continue. If any calls next(err) or sends a response, the chain stops. This pattern is used to compose reusable middleware: authenticate verifies the JWT, authorize('admin') checks the role, and the final handler handles business logic. This keeps each middleware focused on a single concern and makes routes self-documenting about their access requirements.

Open this question on its own page
03

What is the helmet package and why should you use it?

Helmet is an Express middleware collection that sets important HTTP security headers to protect against common web vulnerabilities. Install and use: const helmet = require('helmet'); app.use(helmet());. It sets headers including: Content-Security-Policy (prevents XSS by whitelisting script sources), X-Content-Type-Options: nosniff (prevents MIME type sniffing), X-Frame-Options: DENY (prevents clickjacking), Strict-Transport-Security (enforces HTTPS), and removes the X-Powered-By: Express header (obscures the stack). By default, helmet() enables 11 of 15 available protections. It is considered essential for any production Express application.

Open this question on its own page
04

How do you implement rate limiting in Express.js?

Rate limiting protects your API from abuse, brute force attacks, and DoS. Use the express-rate-limit package: const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); app.use(limiter); — limits each IP to 100 requests per 15 minutes. For login endpoints, apply a stricter limiter. Combine with express-slow-down to progressively slow responses before outright blocking. For distributed systems where multiple server instances share an IP counter, use a Redis store: express-rate-limit supports pluggable stores. Rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) inform clients of their quota.

Open this question on its own page
05

What is input validation and how do you do it in Express?

Input validation ensures data received from users meets your requirements before processing it. Never trust user input — validate at the API boundary. The two most popular approaches: express-validator (integrates tightly with Express): define validation chains like body('email').isEmail().normalizeEmail() in route handlers, then check validationResult(req). Zod or Joi: define a schema and validate req.body against it. These work in middleware that can be reused. Validation should check type, format, length, range, and allowed values. Respond with 400 and detailed error messages when validation fails. Sanitize inputs (strip HTML, trim whitespace) to prevent injection attacks.

Open this question on its own page
06

How do you connect Express.js to MongoDB using Mongoose?

Install Mongoose: npm install mongoose. Connect in your app entry point: mongoose.connect(process.env.MONGODB_URI). Define a Schema and Model: const userSchema = new mongoose.Schema({ name: String, email: { type: String, unique: true } }); const User = mongoose.model('User', userSchema);. Use in route handlers: const users = await User.find(); const user = await User.findById(req.params.id);. Mongoose adds validation, virtuals, middleware hooks (pre/post save), and query building on top of the native MongoDB driver. Store the connection string in .env and never commit it. Handle connection errors and use connection pooling (Mongoose manages this automatically).

Open this question on its own page
07

What is async/await error handling in Express.js?

Express does not automatically catch errors thrown in async route handlers (in Express 4.x). An uncaught async error crashes the process or causes an unhandled promise rejection. The common solution is a wrapper function: const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);. Wrap async handlers: app.get('/users', asyncHandler(async (req, res) => { const users = await User.find(); res.json(users); }));. Any thrown error or rejected promise is automatically passed to next(err). Express 5.x (now in stable) handles this automatically — async route handlers are wrapped by default. Alternatively, use express-async-errors package to patch Express 4.

Open this question on its own page
08

What is the morgan package and how is it used?

Morgan is an HTTP request logger middleware for Express. Install: npm install morgan. Use: const morgan = require('morgan'); app.use(morgan('dev'));. Predefined formats: dev (colorized, concise for development), combined (Apache-style with IP, user agent — for production logs), tiny (minimal). Morgan logs each request with method, URL, status code, response time, and content length. For production, pipe logs to a file or logging service: morgan('combined', { stream: fs.createWriteStream('access.log', { flags: 'a' }) }). Morgan is one of the first middleware to register so it logs all requests, including those that are rejected by later middleware.

Open this question on its own page
09

How do you structure a large Express.js application?

A scalable Express project typically follows a layered architecture: Routes (routes/): define endpoints, validate input, call controllers. Controllers (controllers/): handle HTTP request/response, delegate to services. Services (services/): contain business logic, orchestrate operations. Models (models/): define database schemas. Middleware (middleware/): reusable middleware like auth, validation. Config (config/): environment configuration. Utils (utils/): helper functions. Entry point app.js wires everything together. This separation of concerns makes each layer independently testable and maintainable. Each router file handles one resource, and app.js imports and mounts all routers.

Open this question on its own page
10

What is the dotenv package and how is it used in Express?

dotenv loads environment variables from a .env file into process.env, keeping sensitive configuration (database URLs, API keys, JWT secrets) out of source code. Install: npm install dotenv. At the very top of your entry file: require('dotenv').config();. Your .env file: DB_URL=mongodb://localhost/mydb\nJWT_SECRET=supersecret\nPORT=3000. Access: process.env.DB_URL. Add .env to .gitignore — never commit it. Provide a .env.example with placeholder values as documentation. In production (hosting platforms like Heroku, Render, AWS), set environment variables directly in the platform settings rather than a .env file.

Open this question on its own page
11

How do you implement pagination in an Express REST API?

Pagination limits the amount of data returned per request. The two main approaches: Offset/Page pagination: client sends ?page=2&limit=20. Server calculates skip: const skip = (page - 1) * limit; const items = await Model.find().skip(skip).limit(limit);. Return total count and page metadata. Simple to implement but inefficient for large offsets (database must scan skipped rows). Cursor pagination: client sends a cursor (last item's ID): ?after=lastId. Server queries: Model.find({ _id: { $gt: lastId } }).limit(20). More efficient for large datasets and consistent under concurrent writes. Return a nextCursor in the response. Cursor pagination is preferred for infinite-scroll UIs.

Open this question on its own page
12

What is express-session and how does session-based auth work?

express-session is middleware that stores session data server-side and sends a session ID cookie to the client. Install and configure: app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, store: new RedisStore({ client }) }));. After login: req.session.userId = user.id;. On subsequent requests, the cookie is sent back, the session is loaded, and req.session.userId is populated. req.session.destroy() logs out the user. For production, store sessions in Redis or a database instead of in-memory (default) — in-memory sessions are lost on restart and not shared between multiple server instances. Session-based auth is stateful; JWT is stateless.

Open this question on its own page
13

How do you implement file uploads in Express.js?

Use the multer middleware for handling multipart/form-data file uploads. Install: npm install multer. Configure: const upload = multer({ dest: 'uploads/', limits: { fileSize: 5 * 1024 * 1024 } });. Apply to a route: app.post('/upload', upload.single('avatar'), (req, res) => { res.json({ file: req.file }); });. req.file contains metadata (originalname, mimetype, path, size). Validate file types with Multer's fileFilter option. For production, store files in cloud storage (AWS S3, Cloudinary) rather than the local filesystem — use the multer-s3 storage engine. Always validate file types and sizes to prevent malicious uploads.

Open this question on its own page
14

What is compression middleware in Express and when should you use it?

The compression package adds gzip/deflate/Brotli response compression to Express, reducing bandwidth usage. Install and use: const compression = require('compression'); app.use(compression());. It compresses responses for clients that support it (indicated by the Accept-Encoding header) and sends uncompressed responses otherwise. For JSON APIs, compression can reduce response sizes by 60–80%. It should be near the top of the middleware stack, before response-generating middleware. For high-traffic production apps, offload compression to a reverse proxy like Nginx or a CDN — it is more efficient than compressing at the application layer. Always exclude small responses (under ~1KB) from compression as the overhead outweighs the savings.

Open this question on its own page
15

What is the difference between PUT and PATCH in REST?

PUT is a full replacement operation — it replaces the entire resource with the data sent in the request body. If the client omits a field, the server treats it as null/removed. PUT is idempotent: calling it multiple times with the same data produces the same result. PATCH is a partial update — it applies changes only to the fields included in the request body, leaving other fields unchanged. PATCH is also idempotent in practice but not required to be by spec. In Express: router.put('/:id', replaceUser); router.patch('/:id', updateUserFields);. Most REST APIs use PATCH for update operations because clients rarely need to send the complete resource just to change one field.

Open this question on its own page
16

What is Passport.js and how does it integrate with Express?

Passport.js is authentication middleware for Express that supports 500+ authentication strategies through a unified API. Common strategies: Local (username/password), JWT, OAuth2 (Google, GitHub, Facebook login). Initialize: app.use(passport.initialize());. Define a strategy: passport.use(new LocalStrategy(async (username, password, done) => { const user = await User.findOne({ username }); if (!user || !user.validPassword(password)) return done(null, false); return done(null, user); }));. Protect routes: app.get('/profile', passport.authenticate('jwt', { session: false }), handler);. Passport is flexible and well-documented, making it easy to add social logins or swap authentication mechanisms without changing route code.

Open this question on its own page
17

How do you test an Express.js API?

The standard approach uses Jest (test runner) with Supertest (HTTP assertions). Supertest lets you make HTTP requests to your Express app without starting a real server. Example: const request = require('supertest'); const app = require('./app'); it('GET /users returns 200', async () => { const res = await request(app).get('/users'); expect(res.status).toBe(200); expect(res.body).toBeInstanceOf(Array); });. For database interactions, use a test database, mock the DB layer with Jest mocks, or use in-memory databases. Separate the Express app creation (app.js) from the server startup (server.js) so tests can import the app without starting a listening server. Test both happy paths and error cases.

Open this question on its own page
18

What is Express.js Router-level middleware?

Router-level middleware is middleware bound to an express.Router() instance rather than the app instance. It only runs for requests that match the router's mounted path. This allows you to scope middleware to specific route groups. For example, apply authentication only to API routes: const apiRouter = express.Router(); apiRouter.use(authenticate); apiRouter.get('/users', getUsers); app.use('/api', apiRouter);. All /api/* requests go through authenticate, but public routes mounted directly on app are unaffected. You can stack multiple routers, each with their own middleware chain, enabling fine-grained control over which middleware applies to which routes without cluttering the main app.js.

Open this question on its own page
Advanced 11 questions

Deep expertise questions for senior and lead roles.

01

How do you implement WebSocket support alongside Express.js?

Express handles HTTP, but you can add WebSocket support by sharing the HTTP server. The most common approach uses Socket.io: const server = require('http').createServer(app); const io = require('socket.io')(server); io.on('connection', socket => { socket.on('message', data => io.emit('message', data)); }); server.listen(3000);. Both HTTP (Express) and WebSocket (Socket.io) connections are handled on the same port. Alternatively, use the ws package for raw WebSocket support without the Socket.io abstraction. WebSocket connections are persistent bidirectional channels — unlike HTTP, the server can push data to clients at any time. Use cases: real-time chat, live notifications, collaborative editing.

Open this question on its own page
02

What are security best practices for Express.js APIs?

Production Express security checklist: Use Helmet for security headers. Validate and sanitize all input with Joi or express-validator. Use parameterized queries — never concatenate user input into SQL or MongoDB queries. Rate limit all endpoints, especially auth. Use HTTPS in production. Store secrets in environment variables, never in code. Use bcrypt for password hashing (min cost factor 12). Set HttpOnly, Secure, SameSite flags on cookies. Prevent mass assignment — explicitly whitelist allowed fields. Implement proper CORS restrictions. Avoid revealing stack traces in error responses. Update dependencies regularly and audit with npm audit. Use express-mongo-sanitize to prevent NoSQL injection.

Open this question on its own page
03

How does clustering work in Node.js/Express for better performance?

Node.js is single-threaded, so a single process cannot utilize multiple CPU cores. The cluster module forks multiple worker processes, each running an Express instance and sharing the same TCP/IP port. The master process distributes incoming connections across workers. if (cluster.isMaster) { for (let i = 0; i < os.cpus().length; i++) cluster.fork(); } else { app.listen(3000); }. Workers handle requests independently and restart automatically if they crash. In practice, PM2 (process manager) provides clustering with a simpler interface: pm2 start app.js -i max spawns one worker per CPU. Cluster does not share in-process memory — use Redis for shared state (sessions, caches).

Open this question on its own page
04

What is the difference between Express 4.x and Express 5.x?

Express 5.x (stable since 2024) introduces several important improvements: Async error handling: errors thrown in async route handlers are automatically passed to error-handling middleware without wrapping them manually in try/catch or asyncHandler. Removed deprecated features: req.param(), res.json(status, obj), and other Express 4 deprecations are removed. Router returns a Promise for awaiting route completion. Stricter regex routing: route parameters require explicit delimiters. Path-to-regexp upgrade changes some route matching behavior. Migration from 4 to 5 is mostly straightforward for most apps — the main work is removing deprecated API calls and testing async error propagation behavior.

Open this question on its own page
05

How do you implement a graceful shutdown in Express.js?

A graceful shutdown allows in-flight requests to complete before the process exits, preventing data corruption and incomplete responses during deployments or crashes. Listen for OS signals: process.on('SIGTERM', shutdown); process.on('SIGINT', shutdown);. In the shutdown handler: server.close(() => { db.disconnect(); process.exit(0); });. server.close() stops accepting new connections but lets existing ones finish. Set a timeout to force-exit if connections take too long: setTimeout(() => process.exit(1), 30000);. Also close database connections, flush logs, and deregister from service discovery. Container orchestration systems (Kubernetes) send SIGTERM before killing a container, giving your app the window to shut down cleanly.

Open this question on its own page
06

How do you implement request tracing and correlation IDs in Express?

A correlation ID is a unique identifier attached to every request and propagated through all logs, services, and responses — enabling end-to-end tracing of a request across distributed systems. Implement with middleware: app.use((req, res, next) => { req.correlationId = req.headers['x-correlation-id'] || uuid.v4(); res.set('x-correlation-id', req.correlationId); next(); });. Include the ID in all log lines: logger.info({ correlationId: req.correlationId, msg: 'Processing request' }). Pass it to downstream service calls in request headers. Use AsyncLocalStorage (Node.js 14+) to make the correlation ID available throughout the call stack without threading it through every function parameter, enabling "ambient" request context similar to thread-local storage.

Open this question on its own page
07

What is the Express.js application lifecycle?

Understanding the Express application lifecycle helps diagnose bugs. Startup: app.js executes — middleware is registered, routes are defined, database connections are established. Listening: app.listen() creates an HTTP server and starts the event loop waiting for connections. Request handling: for each request, Express traverses the middleware stack in order — each middleware calls next() or sends a response. If no route matches, the request falls through to a 404 handler. If a middleware calls next(err), error-handling middleware takes over. Response: once res.send() or similar is called, the response is sent. The connection may be kept alive (HTTP/1.1 keep-alive) for subsequent requests. Shutdown: server.close() stops new connections; process exits after in-flight requests complete.

Open this question on its own page
08

What is the role of reverse proxies in Express.js production deployments?

In production, Express typically runs behind a reverse proxy (Nginx, Apache, or a load balancer). The proxy handles: SSL termination (manages HTTPS, Express only sees HTTP internally). Static file serving (far more efficient than Express static middleware). Load balancing across multiple Node.js instances. Compression (gzip at proxy level). Connection limiting and request buffering. Configure Express to trust the proxy's forwarded headers: app.set('trust proxy', 1) — this ensures req.ip returns the client's real IP (not the proxy's) and req.secure correctly reflects HTTPS. Without this setting, rate limiting by IP and HTTPS redirects break behind a proxy.

Open this question on its own page
09

How do you prevent SQL injection in an Express.js application?

SQL injection occurs when user input is concatenated directly into a SQL query, allowing attackers to manipulate the query. Prevention with an ORM like Sequelize or TypeORM: use their query methods and model API — they parameterize queries automatically. With raw SQL, use parameterized queries: db.query('SELECT * FROM users WHERE id = ?', [req.params.id]) — never string concatenation. For MongoDB, use express-mongo-sanitize to strip $ and . characters from user input, preventing NoSQL injection. Additional defenses: validate input types strictly (if id should be an integer, reject non-integers before querying), use least-privilege database accounts, and never expose raw error messages that reveal schema information.

Open this question on its own page
10

What is streaming responses in Express.js and when should you use it?

Streaming responses send data to the client incrementally as it becomes available rather than buffering the entire response. Express responses are Node.js writable streams — you can pipe data directly: const fileStream = fs.createReadStream('large-file.csv'); fileStream.pipe(res);. For database results: stream a MongoDB cursor to the response. For real-time data, use Server-Sent Events (SSE): set Content-Type: text/event-stream and write events with res.write('data: {msg}\n\n'). Benefits: lower Time-To-First-Byte (client starts processing before the full response arrives), lower memory usage (no buffering gigabytes in RAM), real-time delivery for logs and live data. Handle client disconnects with req.on('close', cleanup).

Open this question on its own page
11

How do you implement API versioning in Express.js?

API versioning allows evolving the API without breaking existing clients. Three common approaches in Express: URL path versioning (most common): app.use('/api/v1', v1Router); app.use('/api/v2', v2Router);. Clients explicitly request the version they support. Header versioning: clients send Accept: application/vnd.myapp.v2+json and middleware routes to the appropriate handler. Query string versioning: /api/users?version=2. URL versioning is the simplest and most transparent. Structure v1 and v2 route directories with shared controllers for common logic. When deprecating a version, return a Deprecation response header warning clients, then eventually remove it after sufficient notice.

Open this question on its own page
Back to All Topics 49 questions total