Skip to content
Express.js interview 4 min read

Express Basics Interview Questions

Express is the most common Node.js framework you’ll be asked about in backend interviews, and questions usually start with the fundamentals: what Express actually is, how an app relates to an HTTP server, how routing and middleware work, and how a request flows to a response. The answers below are concise but precise, with runnable code you can reason about out loud. Knowing these cold lets you spend interview time on the harder design questions instead of stumbling on basics.

What is Express and why use it?

Express is a minimal, unopinionated web framework built on top of Node’s native http module. Node alone can serve HTTP, but you’d hand-parse URLs, methods, and bodies yourself. Express adds a thin layer over that: a routing system, a middleware pipeline, and convenience helpers on the request and response objects (res.json, res.status, req.params, and so on).

A strong answer highlights three things: it’s minimal (you compose features from middleware rather than getting a batteries-included framework), it’s middleware-driven (everything is a function in a pipeline), and it’s unopinionated (no enforced project structure or ORM).

import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.json({ message: "Hello from Express" });
});

app.listen(3000, () => console.log("Listening on http://localhost:3000"));

Output:

Listening on http://localhost:3000

Tip: Express 5.x (stable as of late 2024) drops some deprecated APIs and natively forwards rejected promises from async handlers to error middleware. On Express 4.x you must catch async errors yourself or use a wrapper.

Is an Express app the same as an HTTP server?

No, and interviewers like this distinction. express() returns an application object, which is really a request handler function with a fluent API attached. It does not open a socket on its own. Calling app.listen() is a convenience that creates a Node http.Server, registers the app as its handler, and starts listening.

That means you can mount the same app on your own server — useful for HTTPS, WebSockets sharing a port, or testing.

import http from "node:http";
import express from "express";

const app = express();
app.get("/health", (req, res) => res.send("ok"));

// Equivalent to app.listen(3000), but you control the server.
const server = http.createServer(app);
server.listen(3000);
ConceptWhat it isCreated by
appRequest-handler with routing/middleware APIexpress()
serverThe TCP socket listening on a portapp.listen() or http.createServer(app)

How does basic routing work?

A route maps an HTTP method and URL path to one or more handler functions. Express exposes a method per verb — app.get, app.post, app.put, app.delete, app.patch — plus app.all for every method. Handlers receive (req, res, next). Route parameters are declared with a colon and read from req.params; query strings come from req.query.

app.get("/users/:id", (req, res) => {
  const { id } = req.params;        // path segment
  const { fields } = req.query;     // ?fields=name
  res.json({ id, fields });
});

A request to /users/42?fields=name returns:

Output:

{ "id": "42", "fields": "name" }

For larger apps you group routes with the Router, a mountable mini-application, and attach it with app.use.

import { Router } from "express";

const router = Router();
router.get("/", listUsers);
router.post("/", createUser);

app.use("/users", router); // routes become /users and /users with POST

What does app.use do?

app.use registers middleware — a function that runs for matching requests before (or instead of) a route handler. If you pass a path, it only runs for requests whose URL starts with that path; without a path it runs for every request. Middleware that doesn’t end the response must call next() to pass control on, or next(err) to jump to error handling.

app.use(express.json());            // parse JSON bodies, runs for all requests

app.use((req, res, next) => {       // simple request logger
  console.log(`${req.method} ${req.url}`);
  next();                           // hand off to the next handler
});

Order matters: middleware runs top to bottom in the order it’s registered, so body parsers and loggers belong above your routes.

Describe the request-response cycle

When a request arrives, Express matches it against the registered stack in order. Each matching middleware either ends the cycle by sending a response (res.send, res.json, res.end, res.render) or calls next() to continue. The cycle ends the moment a response is sent — anything after that on the same request is ignored (and double-sending throws “headers already sent”).

app.use(express.json());

app.post("/login", (req, res, next) => {
  if (!req.body.email) return next(new Error("Email required"));
  res.status(200).json({ ok: true }); // ends the cycle
});

// Error-handling middleware: four args signals Express it's for errors.
app.use((err, req, res, next) => {
  res.status(400).json({ error: err.message });
});

A POST to /login with an empty body produces:

Output:

{ "error": "Email required" }

Warning: A common bug is forgetting return before next() or res.send(). Without it, code keeps executing and may try to send a second response, crashing the request.

Best Practices

  • Mount body parsers (express.json(), express.urlencoded()) and loggers before your routes so they apply.
  • Group related routes with Router and mount them by prefix to keep files small and paths consistent.
  • Always return after sending a response or calling next() to avoid double responses.
  • Keep handlers thin: validate, delegate to a service layer, then respond — don’t bury business logic in route callbacks.
  • Define error-handling middleware (four arguments) last so all next(err) calls funnel into one place.
  • Prefer Express 5.x for new projects to get automatic async-error forwarding and a cleaner API surface.
Last updated June 14, 2026
Was this helpful?