Express interview revision notes
Node.js web framework: routing, middleware, APIs. Written for developers preparing for an interview who want to refresh their Express fast. Free, no sign-up.
23 topics in these notes
- What is Express?
- Setup & Hello World
- Routing (GET, POST, PUT, DELETE)
- Route parameters & query strings
- Request & Response object
- Middleware (concept, next)
- Built-in middleware (json, static)
- Writing custom middleware
- Error-handling middleware
- Router (express.Router)
- Serving static files
- Body parsing (json, urlencoded)
- REST API design
- Async & errors in route handlers
- Environment & config
- Cookies & sessions
- Security (CORS, helmet)
- File upload (multer)
- Testing (supertest)
- Deployment
- Rate limiting
- Request validation
- JWT authentication
What is Express?
Simple real-life example (Understand first — very important for understanding)
- Think of a reception desk: visitors come with different requests ("I want the contact page," "Save my form," "Give me my data").
- The desk has a simple way to say: "For this type of request, do this." So the app stays organized and easy to extend.
- Express is that kind of framework for web servers: minimal, flexible, and widely used.
Real-world idea: a simple, organized way to handle web requests. Express helps you route and respond without writing everything from scratch.
Technical definition
- Express is a minimal, unopinionated web framework for Node.js. It sits on top of Node's
httpmodule. - Provides: routing, middleware, request/response helpers, static file serving. No database or template engine built-in (you add them).
- Most popular Node framework; used for APIs, SPAs backends, server-rendered apps.
Simple practical example
Provides: routing, middleware, request/response helpers, static file serving.
Code example
// With Express you write routes and middleware instead of
// manually parsing URLs and writing raw res.writeHead + res.end.Why it matters
Backend and full-stack roles use Express constantly. "What is Express?" and "How does it differ from plain Node http?" are common.
Interview tip
"Express is a minimal web framework for Node: routing, middleware, req/res helpers. Unopinionated — you add DB, auth, etc."
Setup & Hello World
Simple real-life example (Understand first — very important for understanding)
- Like setting up a new office: you create the space, decide where the furniture goes, and plug in the basics (e.g. power, network).
- In Express you create an app, attach middlewares (e.g. to read JSON or log requests), and then define routes.
- A few lines of code give you a server that can listen and respond.
Real-world idea: create the app and plug in the essentials. Setup is minimal so you can start adding routes quickly.
Technical definition
npm init -ythennpm install express. Createindex.js;const express = require('express');const app = express().app.get('/', (req, res) => { res.send('Hello') });app.listen(3000). Run withnode index.js.- ESM:
import express from 'express'(with"type": "module"in package.json).
Simple practical example
app.
Code example
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello World'));
app.listen(3000, () => console.log('Listening on 3000'));Why it matters
Starting a server is the first step in any Express interview or project. Know the minimal boilerplate.
Interview tip
"require express, create app, define routes with app.METHOD(path, handler), call app.listen(port)."
Routing (GET, POST, PUT, DELETE)
Simple real-life example (Understand first — very important for understanding)
- Like a signpost: "This path leads to the contact page, that path to the form handler, the other to the API."
- Each route is a pattern (e.g. /about, /users) plus a handler: when a request matches, that handler runs and sends the response.
- You organize the app by URL and method (GET, POST, etc.) so everything has a clear place.
Real-world idea: map addresses to actions. Routing connects each URL (and method) to the right handler.
Technical definition
app.get(path, handler),app.post(path, handler),app.put(path, handler),app.delete(path, handler),app.patch.- Handler:
(req, res) => { }or(req, res, next) => { }. Match first route that matches path and method. app.all(path, handler)— all HTTP methods. Order matters: more specific routes before generic ones.
Simple practical example
Handler: (req, res) => { } or (req, res, next) => { }.
Code example
app.get('/users', (req, res) => res.json(users));
app.post('/users', (req, res) => { /* create */ });
app.put('/users/:id', (req, res) => { /* update */ });
app.delete('/users/:id', (req, res) => { /* delete */ });Why it matters
REST APIs map HTTP methods to actions. GET = read, POST = create, PUT/PATCH = update, DELETE = delete.
Interview tip
"app.METHOD(path, handler). GET read, POST create, PUT/PATCH update, DELETE delete. Route order matters."
Route parameters & query strings
Simple real-life example (Understand first — very important for understanding)
- Like a form with blanks: "Welcome, ____" or "Order number ____." The blank is filled by the actual value in the address.
- Route params are those blanks in the URL (e.g. /users/123 → id is 123). You use them to load the right resource.
- So one route can handle many different IDs or names without writing a separate route for each.
Real-world idea: put changing parts in the URL. Params let one route serve many items (e.g. user 1, user 2) by reading the value from the address.
Technical definition
- Params:
/users/:id→req.params.id. Named segments; e.g./posts/:postId/comments/:commentId. - Query:
?sort=asc&page=2→req.query={ sort: 'asc', page: '2' }. Always strings. - Optional/rest:
/files/:path(*)or regex for complex patterns. Validate and sanitize params/query.
Simple practical example
Query: ?
Code example
app.get('/users/:id', (req, res) => {
const id = req.params.id;
const page = req.query.page || '1';
});Why it matters
APIs use :id for resources and ?key=value for filters, pagination, sort. You must know req.params vs req.query.
Interview tip
"req.params for path segments (:id); req.query for ?key=value. Both can need validation."
Request & Response object
Simple real-life example (Understand first — very important for understanding)
- Like a letter: the request is what the client sent (headers, body, URL, method); the response is what you send back (status, body, headers).
- You read from the request to know what the user wants; you write to the response to send data, HTML, or an error message.
- Everything you need to handle a single visit is in these two objects.
Real-world idea: one object for what came in, one for what you send back. Req and res are the main way you interact with each request.
Technical definition
- req:
req.params,req.query,req.body(after body parser),req.headers,req.method,req.path,req.cookies(with cookie-parser). - res:
res.send(body),res.json(obj),res.status(code),res.sendStatus(code),res.redirect(url),res.setHeader,res.cookie. - Chain:
res.status(201).json({ id: 1 }). End the response once (send, json, redirect, etc.).
Simple practical example
res: res.
Code example
res.status(200).json({ data: users });
res.status(404).json({ error: 'Not found' });
res.redirect(302, '/login');Why it matters
Every handler uses req to read input and res to send output. Knowing status codes and res methods is essential.
Interview tip
"req: params, query, body, headers. res: send, json, status, redirect. Always send one response; chain status().json()."
Middleware (concept, next)
Simple real-life example (Understand first — very important for understanding)
- Like a security check or reception: before you reach the main desk, someone might check your ID, log your visit, or parse your papers.
- Middleware is a step that runs for (almost) every request: it can read and change the request, send a response, or pass control to the next step.
- Order matters: first parse the body, then check auth, then run the route that does the real work.
Real-world idea: run common steps before the main handler. Middleware keeps routing clean and centralizes things like logging and auth.
Technical definition
- Middleware is a function
(req, res, next) => { }. It runs in order; can modify req/res, end the response, or callnext()to pass to the next middleware/route. - If you don't call
next()(or send a response), the request hangs. Callnext(err)to pass to error-handling middleware. - App-level:
app.use(mw)runs for every request. Route-level:app.use('/api', mw)orapp.get('/', mw1, mw2, handler).
Simple practical example
If you don't call next() (or send a response), the request hangs.
Code example
app.use((req, res, next) => {
console.log(req.method, req.path);
next();
});Why it matters
Logging, auth, body parsing, CORS — all middleware. "What is middleware?" and "What happens if you forget next()?" are standard.
Interview tip
"Middleware: (req, res, next). Call next() to continue or send response to end. next(err) for errors. Order matters."
Built-in middleware (json, static)
Simple real-life example (Understand first — very important for understanding)
- Like standard equipment: body parser (read form or JSON from the request), static files (serve images/CSS/JS from a folder), and similar helpers.
- You plug these in once and they work for all routes that need them.
- They solve everyday needs so you don't have to write "read the body" or "serve this file" by hand every time.
Real-world idea: common needs, built-in. Built-in middleware gives you parsing and static files with minimal setup.
Technical definition
express.json()— parses JSON body intoreq.body; use for API JSON payloads.express.urlencoded({ extended: true })— parses form-urlencoded body (e.g. form submit).express.static('public')— serves files from a directory (e.g. CSS, JS, images). Mount at path:app.use('/assets', express.static('public')).
Simple practical example
express.
Code example
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));Why it matters
APIs need express.json() to read POST body. Static files are served with express.static. Must be applied before routes that need req.body.
Interview tip
"express.json() for JSON body; express.static('dir') for static files. Mount middleware before routes that use them."
Writing custom middleware
Simple real-life example (Understand first — very important for understanding)
- Like adding your own checkpoint: "Log every request," "Check if the user is logged in," "Add a request ID."
- You write a function that receives req, res, and next; it does something, then calls next() so the next step runs.
- Custom middleware lets you reuse behavior across many routes without repeating code.
Real-world idea: your own reusable step. Custom middleware is where you put logging, auth, or any shared logic.
Technical definition
- Define a function
(req, res, next) => { ...; next(); }. Attach withapp.use(myMiddleware)or as route handler. - Use for: logging, adding req.user from token, validating API key, request ID, timing. Store data on
reqfor downstream handlers. - Async middleware: wrap in try/catch and call
next(err)in catch, or use a wrapper that catches Promise rejections.
Simple practical example
Use for: logging, adding req.
Code example
const auth = (req, res, next) => {
const token = req.headers.authorization;
if (!token) return res.status(401).json({ error: 'Unauthorized' });
req.user = decodeToken(token);
next();
};
app.get('/me', auth, (req, res) => res.json(req.user));Why it matters
Auth and logging are implemented as middleware. Interview: "How would you add authentication?" — middleware that checks token and sets req.user.
Interview tip
"Custom middleware: (req, res, next), attach with app.use or in route. For async, catch errors and next(err)."
Error-handling middleware
Simple real-life example (Understand first — very important for understanding)
- When something throws or goes wrong, you don't want the whole server to crash; you want to catch it and send a proper response.
- Error middleware is a special handler that receives the error and the request; it can log, format, and send a friendly message or status code.
- You register it last so any unhandled error from earlier steps lands here.
Real-world idea: one place to handle failures. Error middleware keeps the app stable and gives clients a clear error response.
Technical definition
- Error middleware has four args:
(err, req, res, next). Express recognizes it by arity. - Define after other routes:
app.use((err, req, res, next) => { res.status(500).json({ error: err.message }) }). Callnext(err)from any middleware/route to reach it. - Don't forget next in error middleware (or call res.send) or Express may not treat it correctly.
Simple practical example
Define after other routes: app.
Code example
app.get('/fail', (req, res, next) => {
next(new Error('Something broke'));
});
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});Why it matters
Centralized error handling avoids duplicate try/catch. Async errors must be passed with next(err) from catch blocks.
Interview tip
"Error middleware: (err, req, res, next). Define last. Call next(err) from routes/async handlers to trigger it."
Router (express.Router)
Simple real-life example (Understand first — very important for understanding)
- Like splitting a big building into wings: each wing has its own map (routes), but they all belong to the same company.
- Router lets you group routes (e.g. all /api/users, /api/orders) in a separate file or module, then mount that group on the main app.
- So the main app stays small and each feature owns its routes.
Real-world idea: group routes by feature. Router helps you split routes into modules and mount them on the app.
Technical definition
const router = express.Router(). Define routes on router:router.get('/', ...),router.post('/:id', ...).- Mount on app:
app.use('/api/users', router). All router routes are prefixed with/api/users. - Use for splitting by resource (users.js, products.js); each file exports a router; app mounts them.
Simple practical example
Mount on app: app.
Code example
// routes/users.js
const router = express.Router();
router.get('/', (req, res) => { });
router.get('/:id', (req, res) => { });
module.exports = router;
// app.js
app.use('/api/users', require('./routes/users'));Why it matters
Large apps use routers to keep routes organized. "How do you structure Express routes?" — routers per resource, mount under /api/...
Interview tip
"express.Router() for modular routes; mount with app.use('/prefix', router). One router per resource is common."
Serving static files
Simple real-life example (Understand first — very important for understanding)
- Like a public folder in an office: anyone can pick up a brochure, look at a poster, or take a form—no need to ask the receptionist each time.
- Static middleware serves files from a directory (e.g. images, CSS, JS) by URL path, so the browser can load them directly.
- You use it for front-end assets so the server doesn't have to handle every image or script with custom code.
Real-world idea: serve files as-is. Static middleware turns a folder into a simple file server for your assets.
Technical definition
express.static(root)serves files fromroot. First matching file wins. Safe: only serves under root, no path traversal by default.- Mount at path:
app.use('/static', express.static('public'))— URLs like/static/style.css. - Multiple folders:
app.use(express.static('public')); app.use(express.static('uploads')). For SPA: serve index.html for unknown routes if needed.
Simple practical example
Mount at path: app.
Code example
app.use(express.static('public'));
app.use('/files', express.static(path.join(__dirname, 'uploads')));Why it matters
Front-end assets and uploads are served via static. Know how to mount and where to put files.
Interview tip
"express.static('dir') serves files; mount with app.use('/path', express.static('dir')) for a URL prefix."
Body parsing (json, urlencoded)
Simple real-life example (Understand first — very important for understanding)
- When a client sends a form or JSON, the raw data is like a sealed envelope; you need to open it and read the contents before using them.
- Body-parsing middleware reads the request body and puts the result on req.body (e.g. object or form fields) so your routes can use it.
- You must use it for POST/PUT/PATCH if you want to read JSON or form data.
Real-world idea: turn the raw body into something you can use. Body parsing gives you req.body for forms and JSON.
Technical definition
express.json()— Content-Type: application/json →req.bodyobject. Limit:express.json({ limit: '1mb' }).express.urlencoded({ extended: true })— application/x-www-form-urlencoded (form submit).extended: trueuses qs library for nested objects.- Raw/text:
express.raw(),express.text(). Order: put body parsers early so routes have req.body.
Simple practical example
express.
Code example
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true }));
// POST with JSON body → req.body is parsedWhy it matters
APIs expect JSON; forms send urlencoded. Without the right parser, req.body is undefined. Know Content-Type for each.
Interview tip
"express.json() for JSON; urlencoded for form data. extended:true for nested. Place before routes that need req.body."
REST API design
Simple real-life example (Understand first — very important for understanding)
- Like a standard way to ask for and change things: "Get me the list," "Create this," "Update that," "Delete that."
- REST uses URLs for resources and HTTP methods for actions (GET = read, POST = create, PUT/PATCH = update, DELETE = remove).
- Express routes map these to handlers so your API is predictable and easy for clients to use.
Real-world idea: a consistent contract for your API. REST with Express means clear URLs and methods for each action.
Technical definition
- Resources: URLs are nouns (e.g.
/users,/users/1). Verbs = HTTP methods: GET (read), POST (create), PUT/PATCH (update), DELETE (delete). - Status codes: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 500 Server Error.
- Conventions: GET returns resource(s); POST returns 201 + Location + body; PUT/PATCH return updated resource; DELETE returns 204 or 200.
Simple practical example
Status codes: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 500 Server Error.
Code example
GET /users → 200, list
GET /users/1 → 200, one or 404
POST /users → 201, created resource
PUT /users/1 → 200, updated
DELETE /users/1 → 204Why it matters
REST is the standard vocabulary for HTTP APIs. Interviewers expect correct method and status code usage.
Interview tip
"REST: URLs = resources, methods = actions. 200/201/204 for success, 400/401/403/404/500 for errors. Be consistent."
Async & errors in route handlers
Simple real-life example (Understand first — very important for understanding)
- When a handler does async work (database, file, another API), errors might happen later—outside the normal try/catch of the route.
- You need to pass the error to next() so Express can send it to the error middleware instead of leaving the request hanging.
- Wrapping async handlers (or using async/await and try/catch + next(err)) ensures every async error is handled.
Real-world idea: async errors need a path to the error handler. Forwarding them to next(err) keeps your API stable and responsive.
Technical definition
- Async handler:
app.get('/', async (req, res, next) => { await something(); res.json(data); }). Rejected promises don't automatically call next(err). - Options: wrap in try/catch and
next(err)in catch; or use a wrapper:const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next). - Never leave a promise unhandled — either await in try/catch or use the wrapper so errors go to error middleware.
Simple practical example
Options: wrap in try/catch and next(err) in catch; or use a wrapper: const asyncHandler = fn => (req, res, next) => Promise.
Code example
app.get('/user/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
res.json(user);
} catch (e) {
next(e);
}
});Why it matters
Most real routes are async (DB, HTTP). Unhandled rejections crash or hang. "How do you handle async errors in Express?" is common.
Interview tip
"Async routes: try/catch and next(err), or wrap in a function that .catch(next). Never leave rejections unhandled."
Environment & config
Simple real-life example (Understand first — very important for understanding)
- Like different notice boards for dev vs production: port, database URL, and feature flags can change per environment.
- You load these from environment variables or config files so the same code runs everywhere with the right settings.
- Never put secrets in code; keep them in env or a secure config.
Real-world idea: one codebase, many environments. Env and config let you switch settings without changing code.
Technical definition
process.env.NODE_ENV— oftendevelopment,production,test. Load env from.envwithdotenv:require('dotenv').config().- Never commit secrets; use
.env(in .gitignore) andprocess.env.PORT,process.env.DATABASE_URL, etc. - Config object:
const config = { port: process.env.PORT || 3000, db: process.env.DATABASE_URL }. Validate required vars at startup.
Simple practical example
Never commit secrets; use .
Code example
require('dotenv').config();
const PORT = process.env.PORT || 3000;
app.listen(PORT);Why it matters
Different settings per environment; secrets not in code. dotenv and NODE_ENV are standard.
Interview tip
"Use process.env for config; dotenv for .env file. NODE_ENV for environment. Never commit .env."
Security (CORS, helmet)
Simple real-life example (Understand first — very important for understanding)
- Like locks and rules in a building: HTTPS, secure headers, validating input, and not trusting client data protect the app and users.
- Common steps: use Helmet for headers, validate and sanitize input, avoid exposing stack traces, and keep dependencies updated.
- Security is not one feature but a habit: think about every input and every response.
Real-world idea: protect the app and users. Security middleware and good practices reduce risk from the outside world.
Technical definition
- CORS: browsers block cross-origin requests unless server sends
Access-Control-Allow-Origin. Usecorsmiddleware:app.use(cors())orcors({ origin: 'https://app.com' }). - helmet: sets security headers (X-Content-Type-Options, X-Frame-Options, etc.).
app.use(require('helmet')()). - Also: validate/sanitize input, use parameterized queries for DB, rate limiting, HTTPS. Don't expose stack traces in production.
Simple practical example
helmet: sets security headers (X-Content-Type-Options, X-Frame-Options, etc.
Code example
const cors = require('cors');
app.use(cors({ origin: process.env.FRONTEND_URL }));
app.use(require('helmet')());Why it matters
APIs called from another origin need CORS. Security headers and input validation are expected in production.
Interview tip
"CORS for cross-origin requests; helmet for headers. Validate input, parameterized queries, no secrets in responses."
File upload (multer)
Simple real-life example (Understand first — very important for understanding)
- Like dropping off a package at the desk: the client sends a file; the server receives it, checks size and type, and saves it somewhere safe.
- You use middleware (e.g. multer) to handle multipart form data and get the file(s) on req.file or req.files.
- Always validate type and size and store uploads outside the public web root when possible.
Real-world idea: accept files from the client safely. Upload middleware and validation make file handling predictable and safe.
Technical definition
- multer: middleware for multipart/form-data (file uploads).
const upload = multer({ dest: 'uploads/' });app.post('/upload', upload.single('file'), (req, res) => { req.file }). upload.single('fieldName')— one file;upload.array('files')— multiple;upload.fields([{ name: 'a' }, { name: 'b' }]). Validate type/size in production.- req.file:
originalname,mimetype,size,path(temp path). Move to permanent storage and delete temp if needed.
Simple practical example
upload.
Code example
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
console.log(req.file.originalname, req.file.path);
res.json({ ok: true });
});Why it matters
Profile pictures, documents, imports — multer is the standard. Know single vs array and basic validation.
Interview tip
"multer for multipart uploads. single('field') or array('field'). req.file has path, mimetype, size. Validate and limit size."
Testing (supertest)
Simple real-life example (Understand first — very important for understanding)
- Like a rehearsal before the show: you send fake requests to your app and check that the responses and behavior are what you expect.
- You don't need a real browser or network; you call the app with test requests and assert on status, body, and side effects.
- Tests give you confidence when you change code and help document how the API should behave.
Real-world idea: verify behavior with fake requests. Testing your routes and middleware catches bugs before users do.
Technical definition
- supertest: HTTP assertions against Express app.
const request = require('supertest'); const app = require('../app');thenrequest(app).get('/api/users').expect(200).expect('Content-Type', /json/). - Don't call
app.listen()in app when testing — exportappand let supertest handle the server. Or passapptorequest(app). - Use with Jest/Mocha:
it('GET /users returns 200', async () => { await request(app).get('/api/users').expect(200); }). Test status, body, headers.
Simple practical example
Don't call app.
Code example
const request = require('supertest');
const app = require('./app');
const res = await request(app).post('/api/users').send({ name: 'x' }).expect(201);
expect(res.body).toHaveProperty('id');Why it matters
API tests without starting a real server. "How do you test Express routes?" — supertest + Jest/Mocha.
Interview tip
"supertest: request(app).get/post(...).expect(status). Test without listening; export app for tests."
Deployment
Simple real-life example (Understand first — very important for understanding)
- Like moving from a model office to a real building: you need a host, the right port and env vars, and often a process manager to keep the app running.
- Deployment means building (if needed), setting env (e.g. NODE_ENV, database URL), and running the server so it listens and restarts on failure.
- Use a process manager (e.g. PM2) or a platform (e.g. Heroku, Railway) so the app stays up and recovers from crashes.
Real-world idea: run the app in the real world. Deployment is about host, config, and keeping the process running.
Technical definition
- Set
NODE_ENV=production; useprocess.env.PORT. Run withnode index.jsornpm start. - PM2: process manager —
pm2 start index.js --name api; restart on crash, logs, cluster mode.pm2 restart all,pm2 logs. - Reverse proxy (Nginx) in front for SSL, static files, load balancing. Or use platform (Heroku, Railway, Render, AWS) that handles process and env.
Simple practical example
PM2: process manager — pm2 start index.
Code example
pm2 start index.js --name api
pm2 save
pm2 startup # enable restart on rebootWhy it matters
Production: env, PORT, process manager, and proxy are standard. PM2 is often mentioned in Node jobs.
Interview tip
"NODE_ENV=production, use PORT from env. PM2 for process management, restarts, logs. Nginx or platform for SSL and proxy."
Rate limiting
Simple real-life example (Understand first — very important for understanding)
- Like a bouncer at a club: only so many people per minute; beyond that, you ask them to wait or come back later.
- Rate limiting caps how many requests a client (or IP) can make in a time window, so one user or bot can't overload the server.
- You use middleware to count requests and return 429 (Too Many Requests) when the limit is exceeded.
Real-world idea: limit how often someone can knock. Rate limiting protects the server from abuse and ensures fair use.
Technical definition
- Limit how many requests a client (IP or user) can make in a time window. Prevents abuse, DDoS, brute-force. Use
express-rate-limit. const rateLimit = require('express-rate-limit'); app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }))— 100 requests per 15 min per IP. Returns 429 when exceeded.- Can scope by route:
app.use('/api/login', rateLimit({ windowMs: 60000, max: 5 }))for strict login limits. Store in Redis for multi-instance.
Simple practical example
const rateLimit = require('express-rate-limit'); app.
Code example
const rateLimit = require('express-rate-limit');
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: { error: 'Too many requests' }
}));Why it matters
APIs need rate limiting in production. "How do you prevent abuse?" — rate limit by IP or user; 429 Too Many Requests.
Interview tip
"express-rate-limit: windowMs + max; returns 429 when exceeded. Per-route for login etc.; Redis store for multiple servers."
Request validation
Simple real-life example (Understand first — very important for understanding)
- Like checking a form before processing: "Is the email valid? Is the age a number? Are required fields present?"
- Validation rejects bad or dangerous input early and returns clear errors so the client can fix them.
- You validate in middleware or at the start of the route; never trust the request body without checking.
Real-world idea: only accept valid input. Validation keeps bad data out and gives users clear feedback.
Technical definition
- Validate
req.body,req.query,req.paramsbefore business logic. Never trust client input. UseJoi,express-validator, orzod. - express-validator:
body('email').isEmail(),body('age').isInt({ min: 0 }); thenconst errors = validationResult(req); if not empty return 400 with errors. - Validate type, format, length, range; sanitize to prevent XSS/injection. Return clear 400 messages; don't expose internal errors.
Simple practical example
express-validator: body('email').
Code example
const { body, validationResult } = require('express-validator');
app.post('/users', [
body('email').isEmail(),
body('name').trim().isLength({ min: 2 })
], (req, res) => {
const errs = validationResult(req);
if (!errs.isEmpty()) return res.status(400).json({ errors: errs.array() });
// proceed
});Why it matters
Invalid input causes bugs or security issues. "How do you validate API input?" — validation library + 400 on failure. Very common in backend roles.
Interview tip
"Validate body/query/params with Joi or express-validator; return 400 with error list. Sanitize; never trust client input."
JWT authentication
Simple real-life example (Understand first — very important for understanding)
- Like a stamped ticket: the server signs a small packet of data (e.g. user id, expiry); the client sends it back; the server verifies the stamp and trusts the data.
- No need to store sessions on the server for each user—the token carries the claim, and the signature proves it wasn't tampered with.
- Use for APIs and stateless auth: client sends the token in a header; server verifies and then knows who the user is.
Real-world idea: a signed ticket that proves who the user is. JWT gives you stateless authentication for APIs.
Technical definition
- JWT: signed token (header.payload.signature); stateless — server doesn't store session; client sends token in
Authorization: Bearer <token>. - Create:
jwt.sign({ userId, role }, secret, { expiresIn: '7d' }). Verify:jwt.verify(token, secret)→ payload or throw. Use strong secret (env); prefer short expiry + refresh token. - Middleware: extract token from header, verify, set
req.user = payload; if invalid return 401. Protect routes:app.get('/me', authMiddleware, handler).
Simple practical example
Create: jwt.
Code example
const jwt = require('jsonwebtoken');
app.post('/login', (req, res) => {
// ... validate credentials
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});
const auth = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (e) {
res.status(401).json({ error: 'Invalid token' });
}
};Why it matters
Stateless API auth is usually JWT. "How do you implement login for an API?" — issue JWT on login; client sends Bearer token; middleware verifies.
Interview tip
"JWT: sign payload with secret; client sends Bearer token; middleware verifies and sets req.user. Stateless; use env for secret, short expiry."
Practise the questions
Read the top 50 Express interview questions and answers to test yourself after revising.
More Backend & APIs notes
Node.js · REST API · JWT · Google Cloud (GCP) · All developer notes