What you'll learn
Express builds on Node's HTTP server with a routing and middleware model. You will create an Express 5 application, parse JSON deliberately, organize routers, and keep application construction separate from network startup.
By the end of this lesson, you'll be able to:
- Create an Express 5 application using ES modules
- Define routes, parameters, and query input
- Configure JSON parsing and router modules
- Use async route handlers with centralized error flow
Core mental model
Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.
| Concept | What it means | Decision rule |
|---|---|---|
| Application | An ordered middleware and route pipeline | Build and export it without listening |
| Router | A mountable group of related routes | Group by resource or capability, not by HTTP method |
| Route parameter | A named dynamic path segment such as :taskId | Use for resource identity; use query parameters for optional views |
| Async handler | A handler returning a promise | In Express 5, return/await the promise so rejection reaches error middleware |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- Install Express and declare ES module mode
- Create app.js without opening a port
- Register body parsing with an explicit limit
- Mount resource routers under stable prefixes
- Place the 404 handler after all routes
- Place error middleware last
- Listen and handle signals only in server.js
Keep the feedback loop short
Guided code lab
Create an application without listening
Application construction remains importable by tests. express.json is middleware and should use a deliberate limit.
import express from "express";
import { tasksRouter } from "./routes/tasks-router.js";
export function createApp() {
const app = express();
app.disable("x-powered-by");
app.use(express.json({ limit: "100kb" }));
app.get("/health", (request, response) => {
response.json({ status: "ok" });
});
app.use("/api/tasks", tasksRouter);
return app;
}Group task routes in a Router
The mounted prefix stays in app.js while the router owns resource-relative paths.
import { Router } from "express";
export const tasksRouter = Router();
tasksRouter.get("/", (request, response) => {
const completed = request.query.completed;
response.json({ data: [], filter: { completed } });
});
tasksRouter.get("/:taskId", async (request, response) => {
const task = await Promise.resolve({
id: request.params.taskId,
title: "Learn Express routers",
});
response.json({ data: task });
});Own network startup in one entry point
The entry point chooses host and port. The application module remains free of an open socket.
import { createApp } from "./app.js";
const port = Number(process.env.PORT ?? 3000);
const host = process.env.HOST ?? "127.0.0.1";
const app = createApp();
const server = app.listen(port, host, () => {
console.log("API listening at http://" + host + ":" + port);
});
process.once("SIGTERM", () => server.close());
process.once("SIGINT", () => server.close());Production practice
Export the app
Tests should start the app on an ephemeral port; importing application code must not create a hidden listener.
Order deliberately
Express evaluates middleware in registration order, so parsers, routes, 404, and errors must be placed intentionally.
Keep handlers thin
Route handlers translate HTTP to application calls; business rules should remain reusable outside Express.
Common failure mode
Independent workshop
Rebuild the native in-memory task API with Express 5 and resource routers.
Your finished workshop must include:
- createApp and server entry point separation
- A tasks Router mounted under /api/tasks
- Configured JSON limit
- Async handler with a deliberate rejection path
- JSON 404 and error handlers in the correct order
Definition of done
Recap & quick check
Key takeaways
- Express is an ordered middleware pipeline
- Routers group related endpoints
- Path parameters identify resources
- Query parameters shape optional views
- Express 5 forwards rejected async handlers
- App creation and listening are separate responsibilities
Quick check
1. Where should app.listen normally live?
2. What does express.json do?
3. How should routes usually be grouped?
4. What happens to a rejected returned promise in an Express 5 handler?
Next: REST API Design & CRUD