Every Express app, however large, starts from these same few lines.
An Express app is created by calling express(). The result, conventionally named app, is both a request handler function and the object you configure.
app.get(path, handler)registers a route: when aGETrequest matchespath, Express callshandler(req, res).res.send(body)sends a response; it setsContent-Typefrom the body (HTML for strings, JSON for objects) andContent-Length, then ends the response.app.listen(port, callback)creates a Node HTTP server and starts listening. It returns thathttp.Server, which you keep for shutting down later.
Read the port from the environment (process.env.PORT) so hosting platforms can choose it. In Express 5, if the server fails to start — for example the port is already in use (EADDRINUSE) — the error is passed to the listen callback, so check its first argument.
Run the app with node --watch server.js during development to restart on every save.
Two habits worth forming from day one:
- Separate the app from the server. Export
appfromapp.jsand calllisteninserver.js. Tests can then use the app without opening a port. - Every request must get a response. If a handler neither sends a response nor passes control on, the client waits until it times out.
When nothing matches a request, Express answers with a default 404 page ("Cannot GET /nope"). You'll replace that with your own handler later.
Example
// app.js
import express from "express";
export const app = express();
app.get("/", (req, res) => {
res.send("Hello from Express!"); // text/html; charset=utf-8
});
app.get("/health", (req, res) => {
res.json({ status: "ok", uptime: process.uptime() });
});
// server.js — run: node --watch server.js
// import { app } from "./app.js";
const port = Number(process.env.PORT ?? 3000);
const server = app.listen(port, (err) => {
if (err) throw err; // e.g. EADDRINUSE (Express 5)
console.log(`listening on http://localhost:${port}`);
});Remember
express()creates the app;app.getadds a route;app.listenstarts a server- Keep
app(exported) separate fromlistenfor testability - Every request must end with a response
Watch out for
- Hard-coding the port instead of reading
process.env.PORT.