Express is a small web framework for Node.js. Install it with npm install express, create an app with express(), add routes like app.get('/', handler), and start it with app.listen(3000). Middleware such as express.json() runs before your routes, and an error handler with four arguments goes last.
| Need | Code |
|---|---|
| Create the app | const app = express() |
| Handle a GET request | app.get('/users/:id', (req, res) => res.json({ id: req.params.id })) |
| Read a JSON body | app.use(express.json()) |
| Serve a public folder | app.use(express.static('public')) |
| Group routes in a file | const router = express.Router() |
| Catch errors | app.use((err, req, res, next) => { ... }) |
Updated
Most Express tutorials still show version 4 code that breaks or warns on version 5, which is now the default install. This walkthrough uses Express 5 throughout, generates a starter server with the parts you tick, and ends with a table of the changes that trip people up when upgrading an older project.
Create a folder, initialise package.json, mark it as an ES module project and install Express.
mkdir my-api && cd my-api
npm init -y
npm pkg set type=module
npm install express
node index.jsA router is a mini app. Mount it under a prefix so every path inside is relative to it.
// routes/users.js
import { Router } from "express";
const router = Router();
const users = [{ id: "1", name: "Ana" }];
router.get("/", (req, res) => res.json(users));
router.get("/:id", (req, res) => {
const user = users.find((u) => u.id === req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
});
router.post("/", (req, res) => {
const user = { id: String(users.length + 1), name: req.body?.name };
users.push(user);
res.status(201).json(user);
});
export default router;
// index.js
import usersRouter from "./routes/users.js";
app.use("/api/users", usersRouter);In Express 5 a rejected promise in a route is forwarded for you. The four-argument handler, registered last, turns it into a response.
app.get("/boom", async (req, res) => {
throw new Error("Something failed");
});
app.use((err, req, res, next) => {
if (res.headersSent) return next(err);
console.error(err);
res.status(err.status ?? 500).json({ error: "Server error" });
});Express ships codemods for many of these: npx codemod@latest @expressjs/v5-migration-recipe.
| Express 4 | Express 5 | Note |
|---|---|---|
| app.get('/*', ...) | app.get('/*splat', ...) | Wildcards need a name |
| '/:file.:ext?' | '/:file{.:ext}' | Optional parts use braces |
| app.del() | app.delete() | Removed |
| req.param('id') | req.params.id, req.body or req.query | Removed |
| res.sendfile() | res.sendFile() | Removed |
| res.json(obj, 201) | res.status(201).json(obj) | Status no longer a second argument |
| res.redirect('back') | res.redirect(req.get('Referrer') || '/') | Magic 'back' removed |
| req.body is {} when unparsed | req.body is undefined | Check before reading |
| urlencoded extended: true | extended: false by default | Set it if you need nested objects |
| static serves dotfiles | dotfiles ignored by default | Safer default |
A route is a method, a path and a handler. Named segments such as :id are read from req.params, query strings from req.query, and the parsed body from req.body.
Respond with res.json() for data, res.send() for text or HTML, and res.status(404) chained before either to set the code. Express 5 accepts only whole numbers from 100 to 999 in res.status().
Middleware is a function of req, res and next that runs before the route handler. It can read or change the request, end the response, or call next(). Logging, auth checks, CORS headers and body parsing are all middleware, mounted with app.use() for every path or passed to a single route.
Express 5 requires Node.js 18 or newer. If you are stuck on an older Node, stay on Express 4 until you can upgrade the runtime.
No body parser ran for that request. Add app.use(express.json()) for JSON or express.urlencoded() for form posts before your routes, and make sure the client sends a matching Content-Type header. In Express 5 an unparsed body is undefined rather than an empty object.
Wildcards must be named now. Use app.get('/*splat', handler) instead of '/*'. To match the root path as well, wrap it in braces: '/{*splat}'.
It hands the request to the next matching middleware or route. Call next() to continue, next(err) to jump to the error handlers, or send a response to stop the chain. Forgetting both leaves the request hanging.
Not for JSON and form data. express.json() and express.urlencoded() are built in. Note that in Express 5 the urlencoded parser's extended option defaults to false.
Related: Install Node and npm on a Mac, npm commands cheat sheet, HTTP status codes, CORS explained, Next.js API routes.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.