Four arguments
An error handler is recognised by its signature of four parameters, so keep all four even if unused.
Express keeps a Node.js server organised. This guide covers routes, parameters and middleware.
Node.js gives you a runtime for JavaScript on the server. Its built-in http module can serve requests, but you end up matching URLs and parsing bodies by hand. Express is a small framework that handles those chores so you can focus on route logic.
A bare Node.js server checks the request method and URL manually. Express replaces that plumbing with readable route definitions and a request object that is easier to work with.
const express = require("express");
const app = express();
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
app.listen(3000, () => console.log("Listening on port 3000"));
Each route pairs a method and a path with a handler that receives the request and response objects.
Routes are grouped by HTTP method, which maps directly onto what the request does. A GET reads data, and a POST creates it after validating the body.
app.get("/notes", (req, res) => {
res.json(notes);
});
app.post("/notes", (req, res) => {
const note = { id: nextId++, title: req.body.title };
notes.push(note);
res.status(201).json(note);
});
Returning the created record with a 201 status gives the client an identifier it can use immediately.
A route parameter is a named segment in the path, available on req.params. Query strings come after the question mark and appear on req.query.
app.get("/notes/:id", (req, res) => {
const note = notes.find((item) => item.id === Number(req.params.id));
if (!note) return res.status(404).json({ error: "Not found" });
res.json(note);
});
// GET /search?q=express -> req.query.q === "express"
Use parameters to identify a specific resource and query strings for optional filters such as search terms or pagination.
As an app grows, keeping every route in one file becomes unwieldy. express.Router creates a mini application you can mount under a base path.
// routes/notes.js
const router = require("express").Router();
router.get("/", (req, res) => res.json(notes));
module.exports = router;
// app.js
app.use("/notes", require("./routes/notes"));
Each file owns one resource, so the project stays navigable even with many endpoints.
Middleware are functions that run before the final handler. Each receives the request, the response and next, and can inspect, modify or end the request.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
Calling next() passes control to the following middleware or route. Forget it and the request hangs, waiting forever. Middleware runs in the order it is registered, which is why logging and parsing usually appear near the top.
Express does not parse request bodies by default. Add the built-in JSON parser so that req.body is populated for POST and PUT requests.
app.use(express.json());
Register it before the routes that read the body, and always validate what arrives rather than trusting the client.
An error handler is recognised by its signature of four parameters, so keep all four even if unused.
Register it after all routes and middleware so it catches anything they pass along.
Respond with a clear status code and a safe message, and log the details on the server.
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: "Internal server error" });
});
With routes, middleware and one clear error handler, an Express application stays easy to read and safe to extend.