RSRobince Studio

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

  1. What is Express?
  2. Setup & Hello World
  3. Routing (GET, POST, PUT, DELETE)
  4. Route parameters & query strings
  5. Request & Response object
  6. Middleware (concept, next)
  7. Built-in middleware (json, static)
  8. Writing custom middleware
  9. Error-handling middleware
  10. Router (express.Router)
  11. Serving static files
  12. Body parsing (json, urlencoded)
  13. REST API design
  14. Async & errors in route handlers
  15. Environment & config
  16. Cookies & sessions
  17. Security (CORS, helmet)
  18. File upload (multer)
  19. Testing (supertest)
  20. Deployment
  21. Rate limiting
  22. Request validation
  23. 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 http module.
  • 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 -y then npm install express. Create index.js; const express = require('express'); const app = express().
  • app.get('/', (req, res) => { res.send('Hello') }); app.listen(3000). Run with node 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 call next() to pass to the next middleware/route.
  • If you don't call next() (or send a response), the request hangs. Call next(err) to pass to error-handling middleware.
  • App-level: app.use(mw) runs for every request. Route-level: app.use('/api', mw) or app.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 into req.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 with app.use(myMiddleware) or as route handler.
  • Use for: logging, adding req.user from token, validating API key, request ID, timing. Store data on req for 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 }) }). Call next(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 from root. 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.body object. Limit: express.json({ limit: '1mb' }).
  • express.urlencoded({ extended: true }) — application/x-www-form-urlencoded (form submit). extended: true uses 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 parsed

Why 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   → 204

Why 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 — often development, production, test. Load env from .env with dotenv: require('dotenv').config().
  • Never commit secrets; use .env (in .gitignore) and process.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."

Cookies & sessions

Simple real-life example (Understand first — very important for understanding)

  • Like a wristband at an event: the server gives the client a small piece of data; the client sends it back next time so the server "remembers" them.
  • Cookies are that small piece; sessions often use a session ID in a cookie and store the real data (e.g. user id) on the server.
  • Use them for login state, preferences, or anything that should persist across requests.

Real-world idea: remember who the user is across requests. Cookies and sessions give you stateful, personalized responses.

Technical definition

  • Cookies: cookie-parser middleware → req.cookies. Set with res.cookie('name', value, { httpOnly, maxAge }); clear with res.clearCookie('name').
  • Sessions: server-side state (e.g. express-session with store like Redis). Session ID in cookie; server looks up session data. Use for login state.
  • Secure: httpOnly (no JS access), secure (HTTPS only), sameSite for CSRF. For APIs, often use tokens (JWT) in Authorization header instead of session cookies.

Simple practical example

Sessions: server-side state (e.

Code example

const cookieParser = require('cookie-parser');
app.use(cookieParser());
res.cookie('theme', 'dark', { httpOnly: true, maxAge: 86400000 });
req.cookies.theme; // 'dark'

Why it matters

Auth and state: cookies vs sessions vs JWT is a common design question. Know when to use each.

Interview tip

"Cookies: cookie-parser, res.cookie/clearCookie. Sessions: server-side store + session ID in cookie. JWT for stateless API auth."

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. Use cors middleware: app.use(cors()) or cors({ 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'); then request(app).get('/api/users').expect(200).expect('Content-Type', /json/).
  • Don't call app.listen() in app when testing — export app and let supertest handle the server. Or pass app to request(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; use process.env.PORT. Run with node index.js or npm 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 reboot

Why 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.params before business logic. Never trust client input. Use Joi, express-validator, or zod.
  • express-validator: body('email').isEmail(), body('age').isInt({ min: 0 }); then const 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."

More Backend & APIs notes

Node.js · REST API · JWT · Google Cloud (GCP) · All developer notes