# Express.js Cheat Sheet

Course: [Express.js](https://www.devtutorials.ai/learn/expressjs) · Section: Architecture and Reference
Source: https://www.devtutorials.ai/learn/expressjs/architecture-reference/cheat-sheet

> A one-page reference for Express 5: project setup, application settings, request and response methods, routing and Express 5 path syntax, routers, middleware, views, static files, sessions, files, REST status codes, errors, redirects, proxies, logging, security, registration order and an Express 4 to 5 migration checklist.

## Express.js Cheat Sheet

Every important Express 5 API from this course, on one page.

- Each section links back to the lesson that teaches it.
- Code uses ES modules (`import`) and Express 5.
- Copy the starter project at the bottom to begin a new app.

## Project Setup

```bash
mkdir canteen-api && cd canteen-api
npm init -y
npm install express
npm install --save-dev supertest
node --version   # Express 5 needs Node.js 18 or later
```

```json
{
  "type": "module",
  "scripts": {
    "dev": "node --watch --env-file=.env src/server.js",
    "start": "node src/server.js",
    "test": "node --test"
  }
}
```

| Task | Code | Lesson |
|---|---|---|
| Load `.env` (Node 20.6+) | `node --env-file=.env server.js` | Introduction to Express.js |
| Load `.env` with dotenv | `import "dotenv/config";` | Introduction to Express.js |
| Read a setting | `process.env.PORT ?? 3000` | Introduction to Express.js |
| Generate a project | `npx express-generator --view=ejs app` | Application Structure |
| Run TypeScript | `npx tsx src/server.ts` | Express with TypeScript |

## Application

```js
import express from "express";

const app = express();
app.set("json spaces", 2);        // a setting
app.get("json spaces");           // read it (one argument)
app.disable("x-powered-by");
app.locals.school = "Green Valley";

app.listen(3000, (error) => {
  if (error) throw error;         // Express 5: port errors arrive here
  console.log("Ready on http://localhost:3000");
});
```

| Setting | What it does |
|---|---|
| `env` | From `NODE_ENV`. `production` hides error details |
| `x-powered-by` | Adds `X-Powered-By: Express`. Turn it off |
| `json spaces` | Pretty JSON |
| `trust proxy` | Read `X-Forwarded-*` from trusted proxies |
| `case sensitive routing`, `strict routing` | `/Menu` vs `/menu`, `/menu` vs `/menu/` |
| `query parser` | `"simple"` by default in Express 5 |
| `view engine`, `views` | Templates for `res.render` |

## Requests and Responses

| `req` | Gives you |
|---|---|
| `req.method`, `req.path`, `req.originalUrl`, `req.baseUrl` | Method and URL parts |
| `req.params`, `req.query`, `req.body` | Path values, query values, parsed body (all untrusted) |
| `req.get("X-Name")` | One header, any letter case |
| `req.ip`, `req.ips`, `req.protocol`, `req.secure`, `req.hostname` | Connection details |
| `req.is("json")`, `req.accepts("json")` | Body type, preferred answer type |
| `req.cookies`, `req.signedCookies`, `req.session` | With cookie-parser and express-session |

| `res` | Does |
|---|---|
| `res.send(x)`, `res.json(x)`, `res.end()` | Send text, HTML or JSON |
| `res.status(201)`, `res.sendStatus(204)` | Set the status (and send its text) |
| `res.set(name, value)`, `res.append(...)`, `res.type("json")` | Headers |
| `res.locals` | Data for this request only |
| `res.redirect(301, "/menu")`, `res.location(url)` | Redirects and Location |
| `res.cookie(...)`, `res.clearCookie(...)` | Cookies |
| `res.sendFile(name, { root })`, `res.download(path, name)` | Files |
| `res.render("view", data)`, `res.format({...})` | Templates, negotiation |

Rule: one response per request. Use `return res.send(...)` to stop early.

## Routing

```js
app.get("/menu", handler);
app.post("/menu", handler);
app.route("/orders").get(list).post(create);
app.all("/closed", (req, res) => res.sendStatus(503));

app.get("/menu/:category/:id", (req, res) => res.json(req.params)); // strings
app.get("/orders{/:id}", handler);      // optional part
app.get("/files/*splat", handler);      // req.params.splat is an array
app.get("/menu/:id", [checkId, load], show);   // handler chain
app.get("/menu/:id", (req, res, next) => next("route")); // skip to the next route
```

| Express 4 | Express 5 |
|---|---|
| `"*"` | `"/*splat"` |
| `"/:id?"` | `"{/:id}"` |
| `res.redirect(url, 301)` | `res.redirect(301, url)` |
| `res.redirect("back")` | `res.redirect(req.get("Referrer") \|\| "/")` |
| `req.param("id")` | `req.params.id`, `req.query.id` or `req.body.id` |
| Async errors need `next(err)` | Rejected promises reach error middleware |

## Router

```js
const router = express.Router({ mergeParams: true });
router.use(onlyForThisRouter);
router.param("id", (req, res, next, id) => { /* load req.item or 404 */ next(); });
router.get("/", list);
router.get("/:id", show);
export default router;

app.use("/api/v1/menu", router);
```

## Middleware and Body Parsers

```js
function logger(req, res, next) {
  res.on("finish", () => console.log(req.method, req.originalUrl, res.statusCode));
  next();                                   // or send a response, or next(err)
}

const requireRole = (role) => (req, res, next) =>
  req.user?.role === role ? next() : res.status(403).json({ error: "Forbidden" });

app.use(express.json({ limit: "10kb" }));    // application/json
app.use(express.urlencoded());               // HTML forms (extended: false in Express 5)
app.post("/note", express.text(), handler);  // text/plain
app.post("/webhook", express.raw({ type: "application/json" }), handler); // exact bytes
```

| Parser error `err.type` | Status |
|---|---|
| `entity.parse.failed` | 400 |
| `entity.too.large` | 413 |

## Views and Static Files

```js
app.set("view engine", "ejs");               // npm install ejs
app.get("/menu", (req, res) => res.render("menu", { items }));

app.use(express.static(path.join(import.meta.dirname, "public"), { maxAge: "1d" }));
app.use("/assets", express.static("public")); // virtual prefix
app.get("/*splat", (req, res) => res.sendFile(path.join(dist, "index.html"))); // SPA fallback, last
```

| EJS tag | Meaning |
|---|---|
| `<%= value %>` | Print, escaped (safe) |
| `<%- html %>` | Print raw (trusted HTML only) |
| `<% code %>` | Run JavaScript |
| `<%- include("partials/header") %>` | Insert a partial |

## Input, Cookies and Sessions

```js
// Validation middleware: 400 unreadable, 422 broken rules
const validate = (rules) => (req, res, next) => { /* collect errors */ next(); };

app.use(cookieParser(process.env.COOKIE_SECRET));
res.cookie("theme", "dark", { httpOnly: true, secure: true, sameSite: "lax", maxAge: 86400000 });

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: true, sameSite: "lax" },
}));
req.session.regenerate(cb);   // after login
req.session.destroy(cb);      // on logout
```

## Files

```js
const upload = multer({ limits: { fileSize: 2_000_000 }, fileFilter });
app.post("/photo", upload.single("photo"), (req, res) => res.json(req.file));
app.post("/pages", upload.array("pages", 5), handler);   // req.files
res.download(path.join(dir, "menu.pdf"), "canteen-menu.pdf");
```

Multer error codes: `LIMIT_FILE_SIZE` (413), `LIMIT_FILE_COUNT` (400), `LIMIT_UNEXPECTED_FILE` (400).

## Database

```js
import { DatabaseSync } from "node:sqlite";      // Node.js 22.5+
const db = new DatabaseSync("canteen.db");
db.exec("CREATE TABLE IF NOT EXISTS menu (id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER NOT NULL)");
const find = db.prepare("SELECT * FROM menu WHERE id = ?");   // ? = safe parameter
find.get(3);                                                    // one row or undefined
db.prepare("SELECT * FROM menu").all();                         // every row
const { lastInsertRowid, changes } = db.prepare("INSERT INTO menu (name, price) VALUES (?, ?)").run("Idli", 30);
db.exec("BEGIN"); /* ...several writes... */ db.exec("COMMIT"); // or ROLLBACK on error
```

Never build SQL by joining user text. Lesson: **Working with a Database**.

## REST APIs

| Action | Request | Success |
|---|---|---|
| List | `GET /api/v1/menu?veg=true&sort=-price&page=2&limit=10` | 200 `{ data, meta }` |
| Create | `POST /api/v1/menu` | 201 + `Location` |
| Read | `GET /api/v1/menu/3` | 200, or 404 |
| Replace | `PUT /api/v1/menu/3` | 200 |
| Change part | `PATCH /api/v1/menu/3` | 200 |
| Delete | `DELETE /api/v1/menu/3` | 204 |

API docs: serve an OpenAPI file at `/api/openapi.json`, and a try-it page with `swagger-ui-express`.

Errors: 400 bad request, 401 not logged in, 403 not allowed, 404 not found, 409 conflict, 422 invalid, 429 too many, 500 server bug.

## Token Authentication

```js
import jwt from "jsonwebtoken";
const token = jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: "15m" });

function requireAuth(req, res, next) {
  const [scheme, token] = (req.get("Authorization") ?? "").split(" ");
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ["HS256"] });
    next();
  } catch (err) {
    res.status(401).json({ error: err.name === "TokenExpiredError" ? "Token expired" : "Invalid token" });
  }
}
const requireRole = (role) => (req, res, next) => req.user.role === role ? next() : res.status(403).json({ error: "Not allowed" });
```

401 = who are you? 403 = you may not. Check ownership with `req.user.sub`. Lesson: **Token Authentication and Authorization**.

## Errors, Redirects and Proxies

```js
app.use((req, res) => res.status(404).json({ error: "Not found" }));   // after routes
app.use((err, req, res, next) => {                                      // four inputs, last
  if (res.headersSent) return next(err);
  res.status(err.status ?? 500).json({ error: err.status < 500 ? err.message : "Something went wrong" });
});

res.format({ json: () => res.json(data), html: () => res.render("page", data) });
res.redirect(303, "/orders/thanks");                // after a form POST
app.set("trust proxy", 1);                          // one proxy in front
```

| Redirect | Use |
|---|---|
| 301 / 308 | Moved for good (308 keeps the method) |
| 302 / 307 | Temporary (307 keeps the method) |
| 303 | After a form POST |

## Calling APIs, Jobs and Live Updates

```js
const r = await fetch("https://api.weather.lab/today?city=Chennai", { signal: AbortSignal.timeout(2000) });
if (!r.ok) return res.status(502).json({ error: "Weather service failed" });   // fetch does not throw on 4xx/5xx
const data = await r.json();

res.status(202).json({ jobId });                     // slow work: answer now, finish later
sendEmail(order).catch((err) => console.error(err)); // fire-and-forget always needs .catch

res.set("Content-Type", "text/event-stream");        // Server-Sent Events
res.write("data: " + JSON.stringify(update) + "\n\n");
req.on("close", stop);
```

Retry only 503, 429 and network errors, with backoff. Send an `Idempotency-Key` with payments. Lesson: **Calling APIs, Background Jobs and Real-Time**.

## Logging and Security

```js
app.use(morgan("combined", { skip: (req) => req.path === "/health" }));
app.use(helmet());
app.use("/api", cors({ origin: ["https://canteen.school.in"], credentials: true }));
app.use("/login", rateLimit({ windowMs: 15 * 60 * 1000, limit: 5 }));
```

```bash
DEBUG=express:* node server.js     # Express's own debug output
npm audit                          # known problems in packages
```

## Deploying

```js
const port = Number(process.env.PORT ?? 3000);                 // the host picks the port
const server = app.listen(port);
app.get("/healthz", (req, res) => res.json({ status: "ok" }));  // liveness
process.on("SIGTERM", () => server.close(() => process.exit(0))); // finish requests, then exit
```

```bash
NODE_ENV=production node src/server.js
pm2 start src/server.js -i max
docker build -t canteen-api . && docker run -p 3000:3000 -e JWT_SECRET=... canteen-api
```

Lesson: **Deploying an Express App**.

## TypeScript

```bash
npm install -D typescript tsx @types/express @types/node
npx tsx watch src/server.ts      # run while developing
npx tsc --noEmit                 # type-check
npx tsc && node dist/server.js   # build and start in production
```

```ts
import type { Request, Response } from "express";
app.get("/api/menu/:id", (req: Request<{ id: string }>, res: Response<MenuItem | { error: string }>) => { /* ... */ });

declare global { namespace Express { interface Request { user?: { id: string; role: string } } } }
```

Lesson: **Express with TypeScript**.

## Registration Order

1. Settings: `trust proxy`, `x-powered-by`, view engine.
2. Security: `helmet`, `cors`, rate limits.
3. Logging.
4. Body parsers, cookies, sessions.
5. Static files.
6. Authentication.
7. Routers.
8. 404 handler.
9. Error handler.

## Express 4 to 5 Migration Checklist

- Node.js 18 or later.
- Rename wildcards: `"*"` to `"/*splat"`, and optional `?` params to `{/:id}`.
- Remove regex characters `( ) [ ] + !` from string paths.
- Put the status first in `res.redirect(status, url)`. Replace `"back"`.
- Replace `req.param()`, `app.del()` and `res.sendfile()` (lowercase f).
- `req.query` is read-only, and the query parser is `"simple"` by default.
- `express.urlencoded` uses `extended: false` by default, and `req.body` is `undefined` when not parsed.
- Remove `try` / `catch` wrappers that only forwarded async errors. Express 5 does it.
- Check the `app.listen` callback for an `error` argument.

## Starter Project

Press **Download** to get this starter as a zip, with a `package.json` ready for `npm install`.

## What to Remember

- This page is a reminder, not a replacement for the lessons. Follow the lesson names to learn each part.
- When in doubt, check the official Express 5 docs at expressjs.com.

Next: You have finished the course. Build the canteen API from the capstone on your own computer, then deploy it.
