Tutorials Logic, IN info@tutorialslogic.com
Express.js

Top 50 Express.js Interview Questions

Express.js interview questions covering routing, middleware, REST APIs, auth, validation, error handling, security, testing, and deployment.

01

What is Express.js?

Express.js is a minimal, unopinionated web framework for Node.js. It provides routing, middleware support, request and response helpers, error handling, static file serving, and integration points for databases, authentication, validation, and templating. Example: instead of manually checking every URL with Node HTTP APIs, Express lets you define app.get("/users/:id") and attach middleware around it.

02

Why is Express called unopinionated?

Express is called unopinionated because it does not force a fixed project structure, database, validation library, authentication method, or rendering engine. This gives teams flexibility, but it also means senior developers must define conventions for routes, controllers, services, validation, errors, logging, and tests. In interviews, mention that flexibility is powerful only when the team adds structure intentionally.

03

How do you create a basic Express server?

Create an Express app, register routes or middleware, and start listening on a port. A production app usually exports the app separately from the server so tests can import the app without opening a port.

Example
const express = require('express');

const app = express();

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(3000, () => {
  console.log('API running on port 3000');
});
04

What is middleware in Express.js?

Middleware is a function that runs during the request-response cycle. It can read or modify req and res, end the response, or pass control to the next middleware with next(). Common middleware handles JSON parsing, authentication, logging, rate limiting, validation, compression, and error handling.

Example
function requestLogger(req, res, next) {
  console.log(req.method + ' ' + req.originalUrl);
  next();
}

app.use(requestLogger);
05

Why does middleware order matter in Express?

Express executes middleware in the order it is registered. If authentication is registered after protected routes, those routes may be exposed. If the 404 handler is registered before real routes, valid routes may never run. If error middleware is missing or placed incorrectly, errors may leak or crash the process.

Example
app.use(express.json());
app.use('/api', apiRouter);
app.use(notFoundHandler);
app.use(errorHandler);
06

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

app.use() registers middleware for all HTTP methods, optionally under a path prefix. app.get() registers a route handler only for GET requests matching a specific path. Example: app.use("/api", router) mounts a router for many routes, while app.get("/api/users", handler) handles one GET endpoint.

07

How do route parameters work in Express?

Route parameters capture dynamic parts of the URL and expose them through req.params. They are useful for resource identifiers such as user IDs, product IDs, and slugs. Always validate route parameters because they arrive as strings and may be malformed.

Example
app.get('/users/:id', (req, res) => {
  const userId = Number(req.params.id);
  if (!Number.isInteger(userId)) {
    return res.status(400).json({ error: 'Invalid user id' });
  }
  res.json({ id: userId });
});
08

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

req.params contains path variables such as /users/:id. req.query contains URL query string values such as ?page=2&sort=name. req.body contains request payload data, usually parsed from JSON or form data. Example: in POST /users/10/orders?coupon=SAVE, req.params.id is 10, req.query.coupon is SAVE, and req.body may contain order items.

09

How do you parse JSON request bodies in Express?

Use express.json() middleware before routes that need JSON bodies. Configure a reasonable size limit to reduce abuse risk. If the request body is invalid JSON, Express can pass a parsing error to the error handler.

Example
app.use(express.json({ limit: '1mb' }));

app.post('/users', (req, res) => {
  res.status(201).json({ received: req.body });
});
10

How do you create modular routes with express.Router()?

express.Router() lets you group related endpoints into smaller modules. This keeps app.js focused on application setup while route files handle resource-specific routing. In larger apps, route handlers often call controller or service functions instead of containing all business logic inline.

Example
const router = require('express').Router();

router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUserById);

module.exports = router;

// app.js
app.use('/users', require('./routes/users'));
11

What is a controller in an Express project?

A controller is a route handler layer that translates HTTP requests into application actions. It reads inputs from req, calls services, and returns HTTP responses. Controllers should not become dumping grounds for SQL queries, complex business rules, or third-party integration details. Those usually belong in service or repository layers.

12

How do you handle errors in Express?

Centralize error handling with an error middleware function that has four parameters: err, req, res, and next. Route handlers should pass errors to next(err) or use an async wrapper. The error handler should return safe client messages and log useful internal details.

Example
function errorHandler(err, req, res, next) {
  console.error(err);
  res.status(err.status || 500).json({
    error: err.publicMessage || 'Internal server error'
  });
}

app.use(errorHandler);
13

How do you handle async errors in Express?

In Express 4, rejected promises from async handlers usually need to be caught and passed to next(). You can use a small wrapper to avoid repeating try/catch in every route. In Express 5, promise rejection handling is improved, but teams should still understand how their version behaves.

Example
const asyncHandler = fn => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await userService.findById(req.params.id);
  res.json(user);
}));
14

What is a 404 handler in Express?

A 404 handler runs after all valid routes when no route matched the request. It should be registered after routers and before the error handler. For APIs, return JSON with a clear message; for websites, render a not-found page.

Example
app.use((req, res) => {
  res.status(404).json({ error: 'Route not found: ' + req.method + ' ' + req.originalUrl });
});
15

How do you validate request data in Express?

Validate input before business logic runs. Popular choices include Joi, Zod, express-validator, and Yup. Validation should check body, params, and query values. A strong implementation returns a predictable error format so frontend clients and API consumers can handle validation failures reliably.

Example
const { z } = require('zod');

const createUserSchema = z.object({
  name: z.string().min(2),
  email: z.string().email()
});

app.post('/users', (req, res, next) => {
  const result = createUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.issues });
  }
  res.status(201).json(result.data);
});
16

How do you secure an Express application?

Secure Express by validating input, limiting body size, using Helmet for security headers, configuring CORS narrowly, adding rate limits, storing secrets in environment variables, avoiding detailed error leaks, using HTTPS behind the proxy, securing cookies, logging suspicious behavior, and keeping dependencies updated. Security is not one middleware; it is a layered approach.

17

What does Helmet do in Express?

Helmet sets HTTP headers that reduce common web security risks, such as clickjacking, MIME sniffing, and unsafe cross-origin behavior. It is useful as a baseline, but it does not replace authentication, authorization, input validation, CSRF protection, or dependency hygiene.

Example
const helmet = require('helmet');

app.use(helmet());
18

How does CORS work in Express?

CORS controls which browser origins can call your API. It matters for browser-based clients, not server-to-server requests. Avoid allowing every origin with credentials because that can expose authenticated APIs. Configure allowed origins, methods, and credentials based on your frontend environments.

Example
const cors = require('cors');

app.use(cors({
  origin: ['https://example.com', 'https://admin.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true
}));
19

How do you implement rate limiting in Express?

Use rate limiting to reduce brute force attempts, scraping, and accidental overload. Apply stricter limits to login, password reset, OTP, and public search endpoints. In distributed deployments, use a shared store such as Redis instead of per-process memory.

Example
const rateLimit = require('express-rate-limit');

app.use('/login', rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,
  standardHeaders: true,
  legacyHeaders: false
}));
20

How do you implement JWT authentication in Express?

JWT authentication usually reads a bearer token from the Authorization header, verifies its signature, checks important claims, and attaches the authenticated user identity to req. Do not trust decoded token content without verification. Also consider token expiration, refresh strategy, revocation, and where the client stores tokens.

Example
const jwt = require('jsonwebtoken');

function requireAuth(req, res, next) {
  const header = req.get('authorization') || '';
  const token = header.startsWith('Bearer ') ? header.slice(7) : null;
  if (!token) return res.status(401).json({ error: 'Missing token' });

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
}
21

What is the difference between authentication and authorization in Express?

Authentication verifies who the user is. Authorization checks what the authenticated user is allowed to do. In Express, authentication middleware may populate req.user, while authorization middleware checks roles, permissions, ownership, or policy rules before allowing the route handler to run.

Example
function requireRole(role) {
  return (req, res, next) => {
    if (!req.user || req.user.role !== role) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
}

app.delete('/users/:id', requireAuth, requireRole('admin'), deleteUser);
22

How do sessions work in Express?

Sessions store user state on the server and send the browser a session ID cookie. They are useful for traditional web apps and admin panels. In production, use a durable shared session store such as Redis, configure secure cookie flags, and avoid the default in-memory store because it leaks memory and does not work across multiple instances.

23

How should cookies be configured in an Express app?

Cookies that carry sensitive session identifiers should usually be httpOnly, secure in production, sameSite set appropriately, and scoped with a reasonable maxAge. httpOnly reduces JavaScript access, secure requires HTTPS, and sameSite helps reduce CSRF risk depending on the application flow.

Example
res.cookie('sid', sessionId, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'lax',
  maxAge: 7 * 24 * 60 * 60 * 1000
});
24

What is CSRF, and when should an Express app protect against it?

CSRF tricks a browser into sending an authenticated request to a site where the user is already logged in. It is mainly a concern when authentication relies on cookies. Express apps can reduce CSRF with SameSite cookies, CSRF tokens, strict CORS, and avoiding unsafe state changes through GET requests.

25

How do you upload files in Express?

Express does not parse multipart file uploads by default. Use middleware such as multer or busboy. In production, validate file type and size, scan or process files safely, avoid trusting original filenames, and usually store files in object storage rather than keeping them on a single application server.

Example
const multer = require('multer');
const upload = multer({ limits: { fileSize: 5 * 1024 * 1024 } });

app.post('/avatar', upload.single('avatar'), (req, res) => {
  res.json({ filename: req.file.originalname, size: req.file.size });
});
26

How do you serve static files in Express?

Use express.static() to serve files such as images, CSS, JavaScript, and uploads. Static middleware should usually point to a specific public directory, not the whole project. In production, static assets are often served from a CDN or reverse proxy for better caching and performance.

Example
const path = require('path');

app.use('/assets', express.static(path.join(__dirname, 'public'), {
  maxAge: '7d',
  immutable: true
}));
27

How do you connect Express to a database?

Express itself does not manage databases. You connect through a database driver or ORM such as pg, mysql2, Prisma, Sequelize, Mongoose, or Knex. Keep database logic out of route handlers when possible, use connection pooling, handle failures, and close connections gracefully during shutdown.

28

What is connection pooling, and why does it matter?

Connection pooling reuses a limited number of database connections instead of opening a new connection for every request. It improves performance and protects the database from connection storms. A bad pool size can either starve requests or overload the database, so tune it based on database capacity, app instances, and request patterns.

29

How do you prevent SQL injection in Express APIs?

Prevent SQL injection by using parameterized queries, prepared statements, or trusted ORM query builders. Never concatenate untrusted user input into SQL strings. Validation helps, but parameterization is the key defense.

Example
const user = await db.query(
  'SELECT id, email FROM users WHERE email = $1',
  [req.body.email]
);
30

How do you design RESTful routes in Express?

RESTful routes use nouns for resources and HTTP methods for actions. Example: GET /users lists users, POST /users creates a user, GET /users/:id reads one user, PATCH /users/:id updates one user, and DELETE /users/:id removes one user. Avoid routes like /getUsers or /deleteUser when standard HTTP methods already express the action.

31

How do you implement pagination in Express?

Read pagination values from req.query, validate them, enforce maximum limits, and return metadata along with data. Offset pagination is simple, while cursor pagination performs better for large or frequently changing datasets.

Example
app.get('/products', async (req, res) => {
  const page = Math.max(Number(req.query.page) || 1, 1);
  const limit = Math.min(Number(req.query.limit) || 20, 100);
  const offset = (page - 1) * limit;

  const products = await productService.list({ limit, offset });
  res.json({ page, limit, data: products });
});
32

How should API versioning be handled in Express?

A common approach is URL versioning such as /api/v1/users and /api/v2/users. Version only when the contract changes in a breaking way. Keep old versions stable long enough for clients to migrate, document deprecation dates, and avoid duplicating all code if only a small behavior changed.

Example
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
33

How do you document an Express API?

Use OpenAPI or Swagger to describe endpoints, request schemas, response schemas, status codes, authentication, and examples. Documentation is strongest when it is generated or checked from schemas used by the application, because hand-written docs easily drift from real behavior.

34

How do you test Express routes?

Use integration tests with tools such as Supertest to send requests to the Express app without opening a real network port. Tests should cover success responses, validation failures, authentication failures, authorization rules, and error handling.

Example
const request = require('supertest');
const app = require('../app');

test('GET /health returns ok', async () => {
  await request(app)
    .get('/health')
    .expect(200)
    .expect({ status: 'ok' });
});
35

Why should app and server startup be separated in Express?

Separating app creation from server startup improves testability and deployment control. Tests can import app without binding to a port, while server.js can handle listen(), graceful shutdown, and runtime configuration. This pattern also keeps HTTP startup concerns separate from route configuration.

Example
// app.js
const express = require('express');
const app = express();
app.get('/health', (req, res) => res.json({ status: 'ok' }));
module.exports = app;

// server.js
const app = require('./app');
app.listen(process.env.PORT || 3000);
36

How do you add logging in Express?

Use request logging for traffic visibility and structured application logging for business or error events. Morgan is common for request logs, while pino or winston are common for structured logs. Production logs should include request IDs, method, path, status, duration, and user or tenant identifiers when safe.

37

How do you add a request ID in Express?

A request ID helps trace one request across logs, downstream services, and errors. Generate it at the edge or in middleware, attach it to req, include it in response headers, and add it to log context.

Example
const { randomUUID } = require('crypto');

app.use((req, res, next) => {
  req.id = req.get('x-request-id') || randomUUID();
  res.set('x-request-id', req.id);
  next();
});
38

How do you improve Express performance?

Improve performance by avoiding blocking CPU work in request handlers, using compression where appropriate, setting caching headers, validating payload limits, optimizing database queries, using connection pools, serving static assets through a CDN, and monitoring p95 or p99 latency. Express is usually not the bottleneck; slow database calls and heavy synchronous work often are.

39

How do cache headers work in Express responses?

Cache headers tell browsers, CDNs, and proxies how long a response can be reused. Public static assets can often be cached aggressively, while personalized API responses should usually be private or no-store. Good cache policy improves performance without serving stale or private data incorrectly.

Example
app.get('/public-config', (req, res) => {
  res.set('Cache-Control', 'public, max-age=300');
  res.json({ featureFlag: true });
});
40

What is trust proxy in Express?

trust proxy tells Express to trust headers set by a reverse proxy, such as X-Forwarded-For and X-Forwarded-Proto. It matters when the app runs behind Nginx, a load balancer, or a platform proxy. Incorrect trust proxy settings can break secure cookies, IP-based rate limiting, logging, and redirect logic.

Example
app.set('trust proxy', 1);
41

How do you handle graceful shutdown in Express?

Graceful shutdown stops accepting new requests, lets active requests finish for a limited time, closes database connections, flushes logs, and then exits. This matters during deployments and autoscaling because abrupt shutdown can drop requests or leave work half-finished.

Example
const server = app.listen(3000);

process.on('SIGTERM', () => {
  server.close(async () => {
    await db.close();
    process.exit(0);
  });
});
42

What should a health check endpoint include?

A simple liveness check can return that the process is running. A readiness check may verify database connectivity, queue availability, or required dependencies. Do not make every health check expensive. Kubernetes and load balancers may call these endpoints frequently.

Example
app.get('/health/live', (req, res) => res.json({ status: 'alive' }));

app.get('/health/ready', async (req, res) => {
  const dbOk = await db.ping();
  res.status(dbOk ? 200 : 503).json({ database: dbOk ? 'ok' : 'down' });
});
43

How do environment variables fit into Express configuration?

Environment variables store runtime configuration such as PORT, database URLs, JWT secrets, API keys, and environment names. Validate required variables on startup so the app fails fast instead of failing during a request. Never commit secrets to source control.

44

How do you structure a production Express project?

A common structure separates app setup, routes, controllers, services, data access, middleware, configuration, validators, and tests. The exact folders matter less than keeping responsibilities clear. For example, routes map URLs, controllers handle HTTP details, services contain business logic, and repositories or models handle persistence.

Example
src/
  app.js
  server.js
  routes/
  controllers/
  services/
  middleware/
  validators/
  repositories/
  config/
  tests/
45

What are common Express anti-patterns?

Common anti-patterns include putting all logic in app.js, mixing SQL and HTTP response code in the same function, missing centralized error handling, using unvalidated req.body, leaking stack traces to clients, using the default memory session store in production, enabling overly broad CORS, ignoring graceful shutdown, and writing routes that cannot be tested without opening a real server.

46

How do you use dependency injection patterns in Express?

Express does not provide built-in dependency injection, but you can pass dependencies into route factories or controllers. This improves testability because tests can inject fake services without hitting real databases or external APIs.

Example
function createUserRouter({ userService }) {
  const router = require('express').Router();

  router.get('/:id', asyncHandler(async (req, res) => {
    const user = await userService.findById(req.params.id);
    res.json(user);
  }));

  return router;
}

app.use('/users', createUserRouter({ userService }));
47

How do WebSockets work with Express?

Express handles HTTP routes, while WebSockets require an HTTP server plus a WebSocket library such as ws or Socket.IO. You usually create the HTTP server from the Express app, then attach the WebSocket server to it. Keep authentication, scaling, heartbeats, and sticky sessions in mind for production.

Example
const http = require('http');
const { Server } = require('socket.io');

const server = http.createServer(app);
const io = new Server(server);

io.on('connection', socket => {
  socket.emit('message', 'connected');
});

server.listen(3000);
48

What are Server-Sent Events in Express?

Server-Sent Events, or SSE, let the server push one-way text events to the browser over HTTP. They are useful for notifications, progress updates, and live dashboards when the client does not need full bidirectional WebSocket communication.

Example
app.get('/events', (req, res) => {
  res.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive'
  });

  res.write('data: connected\n\n');
});
49

How do you handle large or slow requests in Express?

Set body size limits, use streaming for large files, configure server and proxy timeouts, avoid buffering huge payloads in memory, and return clear errors when limits are exceeded. Slow request handling should also include observability so teams can identify endpoints with high latency or large payload patterns.

50

How would you build a complete Express CRUD endpoint for interviews?

A strong CRUD example validates input, uses clear REST routes, delegates business logic to a service, handles missing resources, and returns correct status codes. The details below show the shape; in a real app, the service would use a database instead of an in-memory array.

Example
const users = [];

app.post('/users', (req, res) => {
  const { name, email } = req.body;
  if (!name || !email) {
    return res.status(400).json({ error: 'name and email are required' });
  }

  const user = { id: String(users.length + 1), name, email };
  users.push(user);
  res.status(201).json(user);
});

app.get('/users/:id', (req, res) => {
  const user = users.find(item => item.id === req.params.id);
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.json(user);
});

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.