Episode 3 — NodeJS MongoDB Backend Architecture / 3.4 — Express JS

3.4 — Express.js: Quick Revision

Episode 3 supplement -- print-friendly cheat sheet.

How to use

Skim -> drill weak spots in 3.4.a through 3.4.f -> 3.4-Exercise-Questions.md -> 3.4-Interview-Questions.md.


Express Setup (Minimal)

npm init -y
npm install express
// app.js
const express = require('express');
const app = express();

app.use(express.json());                          // Parse JSON bodies
app.use(express.urlencoded({ extended: true }));   // Parse form bodies

app.get('/', (req, res) => res.send('Hello'));

module.exports = app;
// server.js
const app = require('./app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

Why separate? app.js exports the app for testing (Supertest). server.js starts the server.


Routing Syntax

// Basic routes
app.get('/api/users', handler);        // Read all
app.get('/api/users/:id', handler);    // Read one
app.post('/api/users', handler);       // Create
app.put('/api/users/:id', handler);    // Replace
app.patch('/api/users/:id', handler);  // Partial update
app.delete('/api/users/:id', handler); // Delete

// Router (modular)
const router = express.Router();
router.get('/', getAllUsers);
router.get('/:id', getUser);
router.post('/', createUser);
module.exports = router;

// Mount in app.js
app.use('/api/users', require('./routes/userRoutes'));

Response Methods Table

MethodContent-TypeUse Case
res.send(string)text/htmlSend HTML or plain text
res.send(object)application/jsonAuto-detects, but prefer res.json()
res.json(object)application/jsonAPI responses (always use for APIs)
res.status(201).json(data)application/jsonSet status code + send JSON
res.sendStatus(204)n/aSend status code with default body
res.redirect(301, '/new-url')n/aRedirect client
res.sendFile(absolutePath)auto-detectedSend a file
res.download(filePath)auto-detectedSend file as attachment (triggers download)
res.set('X-Custom', 'value')n/aSet a response header
res.cookie('name', 'val', opts)n/aSet a cookie

HTTP Methods Table

MethodCRUDIdempotentHas BodyExample
GETReadYesNoGET /api/users/42
POSTCreateNoYesPOST /api/users + JSON body
PUTReplaceYesYesPUT /api/users/42 + full object
PATCHPartial updateYesYesPATCH /api/users/42 + { name }
DELETEDeleteYesOptionalDELETE /api/users/42

Idempotent = same request N times = same result. POST is not idempotent (creates duplicates).


Query Params vs Route Params

Route Params (req.params)Query Params (req.query)
Defined inRoute path: /users/:idQuery string: ?key=value
PurposeIdentify a specific resourceFilter, sort, paginate results
Required?Yes (unless /:id? makes it optional)Always optional
TypeString (always)String (always)
Example URL/api/users/42/api/users?role=admin&page=2
Accessreq.params.id -> '42'req.query.role -> 'admin'
// Combined example
// Route: GET /api/teams/:teamId/members?role=admin&page=1
app.get('/api/teams/:teamId/members', (req, res) => {
  const teamId = req.params.teamId;   // '7'
  const role = req.query.role;         // 'admin'
  const page = Number(req.query.page); // 1
});

Middleware Essentials

// Application-level (runs for ALL requests)
app.use(express.json());
app.use(morgan('dev'));

// Path-specific
app.use('/api', authMiddleware);

// Route-specific (inline)
app.get('/admin', requireAdmin, (req, res) => { ... });

// Error handler (MUST be last, MUST have 4 params)
app.use((err, req, res, next) => {
  res.status(err.statusCode || 500).json({ error: err.message });
});

Order matters: Middleware runs top to bottom. Body parser must come before routes that read req.body.


Static Files Configuration

// Basic: public/css/style.css -> /css/style.css
app.use(express.static(path.join(__dirname, 'public')));

// With prefix: public/css/style.css -> /assets/css/style.css
app.use('/assets', express.static(path.join(__dirname, 'public')));

// With options (production)
app.use(express.static(path.join(__dirname, 'public'), {
  maxAge: '1d',       // Browser cache duration
  dotfiles: 'deny',   // Block .env, .gitignore
  index: false         // Disable directory listing
}));

Always use path.join(__dirname, 'public') -- relative paths resolve from process.cwd(), not from the file location.


SPA Fallback Pattern

// 1. API routes first
app.use('/api', apiRouter);

// 2. Static files second
app.use(express.static(path.join(__dirname, 'public')));

// 3. SPA fallback last -- serve index.html for all unmatched GET requests
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

Order: API routes -> static files -> SPA fallback. The wildcard must be last.


Common Patterns

404 Catch-All

// After all routes, before error handler
app.use((req, res) => {
  res.status(404).json({ status: 'fail', message: 'Route not found' });
});

Consistent API Response Format

// Success
{ status: 'success', data: { user: { ... } } }

// Error
{ status: 'fail', message: 'User not found' }

// Server error
{ status: 'error', message: 'Internal server error' }

Parse + Validate Params

app.get('/api/users/:id', (req, res, next) => {
  const id = Number(req.params.id);
  if (isNaN(id) || id < 1) {
    return res.status(400).json({ error: 'Invalid ID' });
  }
  // proceed...
});

Express vs Raw http Module

FeatureRaw httpExpress
Routingif/else on req.urlapp.get(), app.post(), etc.
Body parsingRead stream + JSON.parse()express.json()
Query paramsurl.parse() or new URL()req.query auto-parsed
Route paramsManual regex/users/:id -> req.params
Response helpersres.writeHead() + res.end()res.json(), res.status()
Static filesManual fs.readFile()express.static()
MiddlewareManual function chainingapp.use() pipeline

Common Errors

ErrorCauseFix
req.body is undefinedMissing express.json()Add app.use(express.json()) before routes
Cannot set headers after they are sentDouble res.send() / missing returnAdd return before res.json() in conditionals
Route not matchingWrong method or pathCheck app.get vs app.post, check path spelling
Static file 404Wrong path or missing middlewareUse path.join(__dirname, 'public')
req.params.id is a stringParams are always stringsCast with Number() or parseInt()
404 handler catches everythingPlaced before routesMove after all route definitions

One-Liners

  • Express = thin wrapper over Node http module. Routing + middleware + helpers.
  • app.use() = register middleware. Runs for every matching request, in order.
  • express.json() = parse JSON bodies. Without it, req.body is undefined.
  • req.params = route segments (:id). req.query = query string (?key=val). Both are strings.
  • res.json() = always use for API responses. Explicit JSON Content-Type.
  • express.static() = serve files from a directory. Use absolute paths.
  • Error handler = 4 params (err, req, res, next). Must be registered last.
  • Separate app.js / server.js = testability. Import app without starting server.

End of 3.4 quick revision.