Lessons available in both languages
MERN Stack · Interview Prep

Express.js interview questions & answers

163+ real Express.js interview questions with model answers, plus free lessons to learn the concepts. Prepare in English & Hinglish, then practise with an AI mock interview.

19 topics · 163+ questions

How Hirenix teaches

One chapter. 90 minutes.
Interview-ready.

Every concept starts with a real-world problem — the kind that actually shows up in production code. Nothing to cram; it just clicks. Every question comes with a model answer: exactly what to say in the room, and why. Then an AI mock interview on the same chapter.

  • 📖Concept in 5 minutesNo jargon — straight to the point
  • 🛠️Real-world problemThe kind production code throws at you
  • 💬Model answerExactly what to say in the room
  • 🧠FlashcardsRevise in 10 minutes
  • 🤖AI mock interviewIt asks follow-ups too
  • 📊Weak topicsSee exactly where you're stuck
Start this chapter — free🌐 English🇮🇳 Hinglish
A student learning an interview concept on Hirenix at home
Video playlistbuilt around a syllabus18h+
Hirenix chapterbuilt around interviews90 min

The difference isn’t the content — it’s the filter. Only what’s actually used in production and actually asked in interviews. Textbook topics the industry never touches don’t make the cut.

Lessons available in both languages

What you’ll learn

  • What is Express?
  • Express vs raw http
  • RoutingFree account
  • Middleware (the core)Free account
  • Request & ResponseFree account
  • Body parsingFree account
  • Route params & queryFree account
  • Router (modular routes)Free account
  • Error-handling middlewareFree account
  • Serving static filesFree account
  • CORSFree account
  • JWT auth middlewareFree account
  • Request validationFree account
  • REST API designFree account
  • HTTP status codesFree account
  • Interview recapFree account
  • Project 1: CRUD REST API
  • Project 2: JWT auth APIFree account
  • Project 3: Middleware pipelineFree account

What is Express?

First — what is an API?

API stands for Application Programming Interface. Strip the jargon and it is simply a contract that lets one program ask another program to do something, without knowing how it does it.

You already use one every day without calling it that. A restaurant menu is an API: it lists what you may order and what you get back. You do not walk into the kitchen, you do not need to know the recipe, and the kitchen is free to change the recipe as long as the dish keeps matching the menu. The menu is the contract; the kitchen is the implementation.

A web API is that same idea over HTTP. Your React app is the customer, your Express server is the kitchen, and the menu looks like this:

GET  /users/42     ->  200  { "id": 42, "name": "Asha" }
POST /users        ->  201  { "id": 43 }

React never learns whether the data came from MongoDB, from a cache, or from another company's server. It only knows the menu. That separation is the whole point — it is why the frontend and backend can be written by different people, in different languages, and deployed on different days.

💡 API vs server vs endpoint. The server is the running program. An endpoint is one item on the menu (GET /users/42). The API is the whole menu — every endpoint plus the rules for calling them.

So when this chapter says "build an API with Express", it means: write the menu, and the kitchen behind it. Express is the tool for doing that.

Imagine you want to open a tea stall. You could build everything from scratch — chop your own wood, forge your own stove, dig a well for water. It works, but you'd spend all day on plumbing instead of making chai. Or you rent a ready-made stall kit: stove, counter, water tap, a menu board already set up. Now you just focus on serving customers. Express is that ready-made kit.

In Express, the raw work is Node's http module — remember the Node project-http-server, where you wrote if (req.url === '/health' && req.method === 'GET') by hand, set headers yourself, and JSON.stringify-ed every response? That's the "forge your own stove" version. Express is a small web framework that sits on top of that same http server and hands you the conveniences: clean routing (app.get('/users', ...)), request/response helpers (res.json(...)), and middleware — so you write far less boilerplate. You start with const app = express(), register routes like app.get(path, handler), and open the door with app.listen(port). Under the hood Express is still calling http.createServer — it doesn't replace Node, it wraps it.

🌍 Real-world example: Raw http needed ~8 lines just to answer GET /health with JSON — manual writeHead, manual Content-Type, manual stringify. In Express it's one line: app.get('/health', (req, res) => res.json({ status: 'ok' })). res.json sets the JSON header AND stringifies for you. Multiply that saving across 30 routes and you see why every MERN backend uses Express.

💡 web framework = a library that gives you a ready structure (routing, middleware, helpers) for building servers, so you write app logic instead of low-level plumbing.

💡 unopinionated = Express doesn't force a folder layout or database on you — you arrange the app however you like (the opposite of an opinionated framework).

💡 middleware = a function that runs on each request between arrival and response — used for logging, auth, body parsing, etc.

Where does it fit? Express is the "E" in MERN (MongoDB, Express, React, Node). React is the frontend the user sees; Express is the API / backend layer — it receives requests from React, talks to the database, and sends JSON back. Node is the runtime it all runs on. So when an interviewer asks "what is Express and why not just use http?", the answer is: it's a minimal framework over Node's http that adds routing, middleware, and req/res helpers so you build APIs faster with less boilerplate.

When to use Express: you are building a JSON API or a small/medium server on Node and you want routing, middleware and helpers without adopting a whole opinionated framework — the default choice for a MERN backend.

When NOT to use it: a single tiny endpoint (raw http is fewer moving parts), or a large app where you actually want structure handed to you — NestJS gives you architecture, and Fastify is worth a look when raw throughput is the constraint. Express's unopinionated nature is a genuine trade-off: it never tells you where anything goes, so on a big team every project invents its own layout.

Standard definition: Express is a minimal, unopinionated web framework for Node.js built on top of the built-in http module, adding routing, middleware, and request/response helpers so you can build servers and APIs with far less boilerplate than raw http.

const express = require('express');
const app = express();

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

app.listen(3000, () => console.log('Server on 3000'));

Express vs raw http

Imagine you need a car. Option one: buy raw parts — engine, wheels, chassis, wiring — and bolt them together yourself. It works, but you write every nut and bolt by hand and it's easy to forget something. Option two: buy a ready-made car where steering, brakes, and dashboard are already wired up — you just drive. That's the difference between Node's raw http module and Express.

In Express, you still get an HTTP server — but the boilerplate is done for you. Where raw http gives you one big handler and you branch on req.url/req.method by hand, Express gives you real routing (app.get('/users', ...), app.post(...)), req/res helpers (res.json(obj) sets the JSON header AND stringifies in one call — no manual writeHead + JSON.stringify), middleware (reusable functions in the request pipeline), and built-in body parsing (express.json() fills req.body). Same server, a fraction of the code.

🌍 Real-world example: A GET /health that returns {"status":"ok"}. In raw http you check req.url === '/health', res.writeHead(200, {'Content-Type':'application/json'}), then res.end(JSON.stringify({status:'ok'})) — three deliberate steps. In Express it's one line: app.get('/health', (req, res) => res.json({ status: 'ok' })). Add a second route in http and you're stacking more if/else; in Express you just add another app.get.

💡 routing = matching an incoming request's URL + HTTP method to the right handler function.

💡 middleware = a function (req, res, next) that runs during the request→response cycle (logging, auth, parsing) before your handler.

💡 body parsing = reading the raw bytes of a request body and turning them into a usable req.body object.

The key point — Express IS Node's http underneath, not a replacement. app.listen(3000) internally calls http.createServer(app) and hands your app to the same http server. req and res are the very same objects the http module gives you, just decorated with extra helpers like res.json and req.params. So Express isn't a new server or a new language — it's a thin, convenient layer over the exact http module you already learned. Knowing the raw version means you know what Express is doing for you.

When raw http is the right choice: a single-endpoint service (a health check, a webhook receiver), a tiny container where you want zero dependencies, or when you are learning and the boilerplate is the lesson. It is also the honest answer in an interview: Express is not required to write an HTTP server in Node.

When to reach for Express: more than a couple of routes, anything needing middleware (auth, logging, CORS, body parsing), or any team project — because "how do we structure routes?" already has a shared answer, and that matters more than the lines saved.

Trade-off: Express costs you a dependency and a layer of indirection. When something behaves oddly, the stack trace now runs through library code, and the answer to "why did this request 404?" lives in a middleware ordering rule rather than in your own if. That is a genuinely good trade for most apps — but it is a trade, which is why knowing the raw http version underneath is what lets you debug the framework instead of guessing at it.

Standard definition: Express is a minimal, unopinionated web framework that runs on top of Node's built-in http module, adding routing, middleware, and request/response helpers (like res.json and req.body) so you write far less boilerplate than raw http.createServer — but it still uses Node's http server underneath, not a replacement for it.

// ---------- RAW HTTP (the hard way) ----------
const http = require('http');
const users = [{ id: 1, name: 'Asha' }];

http.createServer((req, res) => {
  if (req.url === '/users' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(users));      // must stringify + set header by hand
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
}).listen(3000, () => console.log('Server on 3000'));

// ---------- EXPRESS (same server, less code) ----------
const express = require('express');
const app = express();
app.use(express.json());                 // built-in body parsing -> req.body

app.get('/users', (req, res) => res.json(users));            // routing + res.json
app.post('/users', (req, res) => {
  const user = { id: users.length + 1, name: req.body.name };
  users.push(user);
  res.status(201).json(user);            // status + json chained
});

app.listen(3000, () => console.log('Server on 3000')); // internally uses http.createServer

Project 1: CRUD REST API

What we're building: A full CRUD REST API for a /todos resource with Express — list all, get one, create, update, delete. Data lives in a plain in-memory array (a real database is the natural next step, noted at the end). This is THE resume project for a backend fresher: it wires together express.json() body parsing + the 5 CRUD routes + req.params/req.body + correct status codes + a 404 handler — the exact skeleton every backend job expects you to build in an interview.

By the end you'll trace a request straight through: request comes in → Express matches the route → the handler reads req.params/req.body → we mutate the array → res.status(code).json(...) replies. If you did the Node project-file-todo-api (the raw http version), watch how Express deletes almost all the plumbing you wrote by hand.

🌍 Real-world example: This is exactly what a to-do app's backend does. The frontend sends POST /todos with {"title":"buy milk"}, the server creates it and replies 201 Created with the new todo (including its id). Later it sends GET /todos to show the list. Swap the array for a Mongo/Postgres call and this is production.

💡 CRUD = Create, Read, Update, Delete — the four things you do with any resource. In REST they map to POST, GET, PUT/PATCH, DELETE on a noun URL like /todos.

💡 REST API = a set of URL + HTTP-method rules for working with data. The method is the verb (GET read, POST create…), the URL is the noun (/todos, /todos/3).


Step 0 — The mental model (read this first)

In raw Node you wrote one giant handler and matched req.method + req.url with a wall of ifs, split the URL by hand for :id, and collected the body in stream chunks. Express replaces all three:

  • Routingapp.get('/todos', handler), app.post('/todos', handler). No method/url ifs. Express matches the verb + path and calls the right function.
  • Route params — a :id in the path (/todos/:id) shows up parsed on req.params.id. No url.split('/').
  • Body parsingapp.use(express.json()) reads the JSON body for you and hands it over as req.body. No req.on('data') chunk-collecting.

And replies get shorter: res.status(201).json(obj) sets the status and sends JSON in one chainable line. Same REST concepts, a fraction of the code.


Step 1 — The app + express.json()

Every Express app starts the same three lines:

const express = require('express');
const app = express();

app.use(express.json()); // parse JSON bodies into req.body

What's happening:

  1. express() creates the app — an object that is also the request handler Node's http server will call. (Under the hood app.listen still uses http.createServer — Express sits on top of the same http module you learned.)
  2. app.use(express.json()) registers a middleware that runs on every request before your routes. It looks at requests with Content-Type: application/json, reads the body, JSON.parses it, and sets req.body. Without this line, req.body is undefined — the single most common Express gotcha.
  3. Order matters: express.json() must be registered above the routes that read req.body, because middleware runs top-to-bottom.

💡 Middleware = a function that runs during the request→response cycle, before (or instead of) your route handler. express.json() is a built-in one; you can write your own (req, res, next) => {}.


Step 2 — Our data + the 5 CRUD routes

We keep todos in a normal array and an id counter — no file, no DB (yet):

let todos = [];
let nextId = 1;

Now the five routes. Each maps one HTTP verb to one CRUD action:

// READ all
app.get('/todos', (req, res) => {
  res.status(200).json(todos);
});

// READ one
app.get('/todos/:id', (req, res) => {
  const id = Number(req.params.id);
  const todo = todos.find(t => t.id === id);
  if (!todo) return res.status(404).json({ error: 'Todo not found' });
  res.status(200).json(todo);
});

// CREATE
app.post('/todos', (req, res) => {
  const { title } = req.body;
  if (!title) return res.status(400).json({ error: 'title is required' });
  const todo = { id: nextId++, title, done: false };
  todos.push(todo);
  res.status(201).json(todo);
});

// UPDATE
app.put('/todos/:id', (req, res) => {
  const id = Number(req.params.id);
  const todo = todos.find(t => t.id === id);
  if (!todo) return res.status(404).json({ error: 'Todo not found' });
  if (req.body.title !== undefined) todo.title = req.body.title;
  if (req.body.done !== undefined) todo.done = req.body.done;
  res.status(200).json(todo);
});

// DELETE
app.delete('/todos/:id', (req, res) => {
  const id = Number(req.params.id);
  const before = todos.length;
  todos = todos.filter(t => t.id !== id);
  if (todos.length === before) return res.status(404).json({ error: 'Todo not found' });
  res.status(204).end();
});

What's happening — the whole CRUD table at a glance:

  • GET /todos → read the list → 200 OK with the array.
  • GET /todos/:idreq.params.id gives the id → find it → 200 with the todo, or 404 if it's not there.
  • POST /todos → read req.body.title → validate → create → 201 Created (the "I made a new resource" code).
  • PUT /todos/:id → find + update the given fields → 200, or 404.
  • DELETE /todos/:id → remove it → 204 No Content (success, nothing to send back), or 404.

Notice: no url.split, no req.on('data'), no manual Content-Type header. Express gave us req.params.id, req.body, and res.json().

💡 res.status(code).json(obj) is chainablestatus() returns res, so you set the code and send the body in one line. res.json() also sets Content-Type: application/json for you.

💡 204 No Content for DELETE: the operation succeeded but there's nothing meaningful to return, so we send an empty body with .end(), not .json().


Step 3 — TRACE a POST end to end (body → validate → create → 201)

This is the interview money-shot. Follow every beat — client sends POST /todos with {"title":"buy milk"}:

  1. Middleware runs firstexpress.json() sees Content-Type: application/json, reads the body stream, JSON.parses it, and sets req.body = { title: 'buy milk' }. Then it calls next(), handing control to the router.
  2. Route match → Express sees POST + /todos and calls the app.post('/todos', ...) handler. (No if (method === 'POST' && url === '/todos') — the framework matched it.)
  3. Read the bodyconst { title } = req.body pulls out 'buy milk'. This only works because express.json() ran in step 1 — remove that middleware and req.body is undefined and this throws.
  4. Validate → no title? return res.status(400).json({ error: 'title is required' }) and stop. (Client sent bad data → 400.) The return is important — it stops the handler so we don't also send a 201.
  5. Create → build { id: 1, title: 'buy milk', done: false } (id from nextId++, which is 1 then becomes 2) and push it into todos.
  6. Replyres.status(201).json(todo) — status 201 Created, body is the new todo with its id. The client now knows the id it can use for future GET/PUT/DELETE.

Prove it with a read-back: the client sends GET /todos. That handler runs res.status(200).json(todos) → the array now holds [{ id: 1, title: 'buy milk', done: false }] → returned. (In-memory means it's gone on restart — that's the one thing a real DB fixes, and the reason Step 5 points there.)

💡 Contrast with raw Node: in the http version this same POST needed a hand-written parseBody(req) that collected req.on('data') chunks and JSON.parsed on 'end', plus a manual if (method === 'POST' && url === '/todos'). Express did both for free — that's the whole point of the framework.


Step 4 — TRACE a GET /:id end to end (param → find → 200 or 404)

Client sends GET /todos/1:

  1. Route match → Express matches GET against the pattern /todos/:id. The :id segment is a route param; Express parses 1 out of the path and sets req.params = { id: '1' }.
  2. Read the paramconst id = Number(req.params.id). Route params are always strings ('1'), so we Number() it to 1 to compare against the numeric todo.id. (Same reason query values are strings.)
  3. Findtodos.find(t => t.id === id) looks for a todo whose id matches.
  4. Branch on the result:
    • Foundres.status(200).json(todo)200 OK with that single todo.
    • Not foundreturn res.status(404).json({ error: 'Todo not found' })404, because the id doesn't exist. The return stops us from falling through and sending a second response (calling res twice throws "Cannot set headers after they are sent" — another classic gotcha).

Compare to raw Node: there you did Number(url.split('/')[2]) to dig the id out of /todos/1. Express handed it to you as req.params.id from the :id in the route path. The router does the URL parsing.


Step 5 — The 404 handler + a note on DB

What if the client hits a route we never defined, like GET /users? Add a catch-all at the very bottom, after all real routes:

// 404 — no route matched
app.use((req, res) => {
  res.status(404).json({ error: 'Route not found' });
});

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

What's happening: middleware runs top-to-bottom. If a request doesn't match any of the five routes above, it falls through to this final app.use(...), which replies 404. Because it has no path, it matches everything that got this far — so it must be registered last, or it would swallow requests meant for real routes (middleware-order gotcha).

The DB next step: right now let todos = [] lives in memory, so every restart wipes the data (and it doesn't scale past one server process). The natural upgrade is to replace the array with a database: todos.find(...) becomes Todo.findById(id) (Mongoose/MongoDB) or a SELECT ... WHERE id = $1 (Postgres). Every route's shape stays identical — the handler, status codes, and req.params/req.body don't change; only the data-access line does. That's why this array version is a perfectly honest resume project: it is the real structure, minus one swap.


🔎 The full flow (recap the wiring)

Every route is the same three-beat song, now Express-short:

  1. Match — Express routes the verb + path to your handler (app.get/post/put/delete). No manual ifs.
  2. Read + changereq.params.id (which resource) and req.body (the new data, thanks to express.json()); mutate the array (find/push/filter).
  3. Replyres.status(code).json(...) with the right code: 200 read/update, 201 create, 204 delete, 400 bad input, 404 not found.

🚀 From raw Node to Express — what disappeared

Raw http version Express version
if (method === 'GET' && url === '/todos') app.get('/todos', ...)
Number(url.split('/')[2]) req.params.id
hand-written parseBody(req) (stream chunks) app.use(express.json())req.body
res.writeHead(201); res.end(JSON.stringify(x)) res.status(201).json(x)
res.setHeader('Content-Type', ...) on every reply res.json() sets it for you

Same REST verbs, same status codes, same data logic — Express just deleted the boilerplate. That's why we built it the hard way first: now you know exactly what Express is doing under the hood.


✅ What you just learned

  • A full CRUD REST API in Expressapp.get/post/put/delete mapping the five verbs to Create/Read/Update/Delete on /todos.
  • express.json() — the built-in middleware that populates req.body; without it req.body is undefined.
  • req.params vs req.body — the id from the URL path (:id) vs the JSON payload; params are strings, so Number() them.
  • Correct status codes200 read/update, 201 create, 204 delete, 400 bad input, 404 not found.
  • A 404 catch-all registered last so it only catches truly unmatched routes.
  • The return res... habit so you never send two responses ("headers already sent").
  • The bridge from raw Node — Express removes routing + body-parsing boilerplate; the DB swap is the next step.

Standard definition: A REST CRUD API in Express exposes one resource through the HTTP verbs — GET to read, POST to create, PUT to replace, PATCH to update part, DELETE to remove — parsing JSON bodies with express.json(), returning the status code that matches the outcome (201 on create, 204 on delete, 404 when the resource does not exist), and handling errors in a final four-argument middleware.

const express = require('express');
const app = express();

app.use(express.json()); // parse JSON bodies into req.body

let todos = [];
let nextId = 1;

// READ all
app.get('/todos', (req, res) => {
  res.status(200).json(todos);
});

// READ one
app.get('/todos/:id', (req, res) => {
  const id = Number(req.params.id);
  const todo = todos.find(t => t.id === id);
  if (!todo) return res.status(404).json({ error: 'Todo not found' });
  res.status(200).json(todo);
});

// CREATE
app.post('/todos', (req, res) => {
  const { title } = req.body;
  if (!title) return res.status(400).json({ error: 'title is required' });
  const todo = { id: nextId++, title, done: false };
  todos.push(todo);
  res.status(201).json(todo);
});

// UPDATE
app.put('/todos/:id', (req, res) => {
  const id = Number(req.params.id);
  const todo = todos.find(t => t.id === id);
  if (!todo) return res.status(404).json({ error: 'Todo not found' });
  if (req.body.title !== undefined) todo.title = req.body.title;
  if (req.body.done !== undefined) todo.done = req.body.done;
  res.status(200).json(todo);
});

// DELETE
app.delete('/todos/:id', (req, res) => {
  const id = Number(req.params.id);
  const before = todos.length;
  todos = todos.filter(t => t.id !== id);
  if (todos.length === before) return res.status(404).json({ error: 'Todo not found' });
  res.status(204).end();
});

// 404 — no route matched (register LAST)
app.use((req, res) => {
  res.status(404).json({ error: 'Route not found' });
});

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

Express.jsinterview questions & answers

10 sample questions below — 163+ in the full bank inside.

What is error-handling middleware in Express?

It is a special middleware with the four-argument signature (err, req, res, next). Express routes any error passed via next(err) to it, so instead of every route repeating try/catch logic, you handle all errors in one central place and send a clean response.

In simple terms: Think of a factory conveyor belt: normal middleware are the workers passing the item along, and the error handler is the reject bin at the very end where any faulty item is dropped. Example: app.use((err, req, res, next) => res.status(500).json({ error: err.message })); every next(err) in your app lands here.

How does Express know a middleware is an error handler?

Purely by the number of parameters — if the function declares exactly four (err, req, res, next), Express treats it as an error-handling middleware. Drop the err and it becomes a normal three-arg middleware, so the count is not optional.

In simple terms: It is like a form with a mandatory 'complaint' box: a form with that extra box goes to the complaints desk, one without it goes to the normal counter. Example: (err, req, res, next) => {} is an error handler; (req, res, next) => {} is a regular one — same body, different desk.

What is the same-origin policy?

The same-origin policy is a browser security rule that stops a web page from reading responses of requests made to a different origin than the page itself. An origin is the combination of protocol + domain + port; if any of those three differ, it is a different origin. It exists to protect users so a malicious site can't silently read data from another site you're logged into.

In simple terms: Think of origins like separate apartments in a building — you can knock on any door, but you can't just walk in and read someone else's mail. The browser enforces the walls. Example: http://site.com and https://site.com are different origins (protocol differs), and so are site.com:3000 and site.com:5000 (port differs).

What does express.json() do?

express.json() is a built-in middleware that reads the incoming request body, and if its Content-Type is application/json, parses the JSON text into a JavaScript object and puts it on req.body. You register it once with app.use(express.json()).

In simple terms: It's a translator that turns JSON text coming over the wire into a real JS object you can use. Text comes in, object goes onto req.body. Example: app.use(express.json()); then in a handler req.body is the parsed object like { email: 'a@b.com' } instead of undefined.

When would you use express.urlencoded() instead of express.json()?

express.urlencoded() parses bodies sent as application/x-www-form-urlencoded — the format a classic HTML <form> submits. Use it for traditional form posts; use express.json() when the client sends JSON (like a fetch/axios call with a JSON body).

In simple terms: A plain HTML form doesn't send JSON — it sends data like name=Ali&age=22 (key=value pairs joined by &). express.urlencoded() decodes that string back into req.body. Example: app.use(express.urlencoded({ extended: true })) turns name=Ali&age=22 into req.body = { name: 'Ali', age: '22' }.

How do you enable CORS in an Express app?

Install the cors package and register it as middleware with app.use(cors()) before your routes. It automatically adds the Access-Control-Allow-Origin (and related) headers to responses. You can pass options like cors({ origin: 'http://localhost:3000' }) to whitelist a specific origin instead of allowing everyone.

In simple terms: The cors middleware is like hiring a doorman who stamps the right 'allowed' sticker on every response so the browser lets it through. Example: const cors = require('cors'); app.use(cors({ origin: 'http://localhost:3000' })); Now requests from the React app on :3000 are allowed.

What is the difference between express.json() and express.urlencoded()?

Both are built-in body parsers, but they handle different Content-Types. express.json() parses application/json bodies; express.urlencoded() parses application/x-www-form-urlencoded (HTML form) bodies. Apps often register both so they accept either format.

In simple terms: Same job (fill req.body), different input formats — like having two chargers, one USB-C and one micro-USB, for two kinds of phones. Example: app.use(express.json()); app.use(express.urlencoded({ extended: true })); now the server understands both JSON API calls and plain form submits.

You send a POST request with a JSON body but req.body is undefined. Why?

Express does not parse the request body automatically. Without a body-parsing middleware, req.body stays undefined. Adding app.use(express.json()) before your routes parses the incoming JSON into req.body.

In simple terms: The request body arrives as a raw stream of bytes; Express won't turn it into a usable object unless you tell it to. Think of a sealed courier parcel — the postman drops it at your door but won't unwrap it; express.json() is the person who opens it and hands you the contents. Example: after app.use(express.json()), a POST with {"name":"Ali"} gives you req.body.name === 'Ali'.

What is CORS?

CORS (Cross-Origin Resource Sharing) is a browser mechanism that lets a server explicitly allow requests coming from a different origin (different protocol, domain, or port). By default browsers block cross-origin requests due to the same-origin policy; CORS is the controlled way to relax that by having the server send Access-Control-Allow-Origin headers.

In simple terms: Think of it like a guest list at a party: your API is the party, and by default only guests from the same house (same origin) get in. CORS is the host adding other addresses to the guest list. Example: an API at http://localhost:5000 can send Access-Control-Allow-Origin: http://localhost:3000 to let the React app on :3000 in.

Where in the file should error-handling middleware be registered?

It must be registered LAST — after all your routes and other app.use() calls. Middleware runs top-to-bottom, so the error handler has to sit at the bottom of the pipeline to catch errors bubbling up from anything above it.

In simple terms: Like a safety net under a trapeze: it only works if it is placed below the acrobats, not above them. Example: define app.get('/users', ...) first, and app.use((err, req, res, next) => {...}) as the very last app.use — otherwise routes defined after it never reach it.

153+ more Express.js questions inside

Create a free account to read the full question bank, learn every topic, and practise with an AI mock interview.

Unlock all questions — free

Ready to practise Express.js?

Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.