Built-in Middleware
Express ships with a small set of middleware you can use without installing anything extra. These cover the most common needs of a real application: parsing JSON request bodies, parsing HTML form submissions, and serving static assets like images, CSS, and client-side scripts. Knowing exactly what each one does — and when to enable it — keeps your app lean and avoids the classic “why is req.body undefined?” surprise.
What “built-in” means now
Historically you had to install the separate body-parser package to read request bodies. As of Express 4.16.0, body-parser was folded back into the core, so express.json() and express.urlencoded() are available directly on the express object. You no longer need a dependency for ordinary parsing — though the standalone body-parser package still exists if you want it.
There are three built-in middleware factories, each a function that returns a middleware:
| Middleware | Purpose | Replaces |
|---|---|---|
express.json() | Parses application/json request bodies into req.body | body-parser.json() |
express.urlencoded() | Parses application/x-www-form-urlencoded bodies into req.body | body-parser.urlencoded() |
express.static() | Serves files from a directory (images, CSS, JS) | serve-static |
None of these are enabled by default. Express does no body parsing out of the box — you must mount the parser yourself, or
req.bodystaysundefined.
express.json()
Mount express.json() to accept JSON payloads from clients (the typical case for REST APIs and fetch/axios requests). It only runs when the incoming Content-Type is application/json; other content types pass through untouched.
import express from "express";
const app = express();
app.use(express.json());
app.post("/users", (req, res) => {
// req.body is the parsed JSON object
res.status(201).json({ created: req.body });
});
app.listen(3000);
A request like:
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","role":"admin"}'
Output:
{"created":{"name":"Ada","role":"admin"}}
Useful options:
| Option | Default | Description |
|---|---|---|
limit | "100kb" | Maximum body size; oversized requests get a 413 |
strict | true | Only accept arrays and objects as the top-level JSON value |
type | "application/json" | Content types to parse (string, array, or function) |
reviver | undefined | Passed through to JSON.parse |
app.use(express.json({ limit: "1mb" }));
express.urlencoded()
This parses bodies submitted by HTML forms (Content-Type: application/x-www-form-urlencoded). The most important option is extended, which selects the parsing library.
extended: falseuses thequerystringlibrary — values are always strings, no nested objects.extended: trueuses theqslibrary, allowing rich nested objects and arrays (e.g.user[name]=Ada).
app.use(express.urlencoded({ extended: true }));
app.post("/login", (req, res) => {
res.send(`Hello, ${req.body.username}`);
});
curl -X POST http://localhost:3000/login \
-d "username=ada&password=secret"
Output:
Hello, ada
In Express 5,
extendeddefaults tofalse. In Express 4 it has no default and you should always set it explicitly to silence the deprecation warning.
express.static()
express.static() serves files from a directory directly, without writing a route for each one. Point it at a folder and any file inside becomes reachable by its path.
app.use(express.static("public"));
With a file at public/css/site.css, a request to /css/site.css returns that file. Mount it under a virtual prefix to namespace the URLs:
app.use("/assets", express.static("public"));
// public/logo.png is now served at /assets/logo.png
Common options include maxAge for cache headers, index to set the directory index file, and dotfiles to control access to hidden files:
app.use(
express.static("public", {
maxAge: "1d",
index: "index.html",
dotfiles: "ignore",
})
);
Use an absolute path in production. A relative path is resolved against the process’s current working directory, not the file, so
express.static(path.join(import.meta.dirname, "public"))is the safe form.
Ordering matters
Built-in middleware runs in the order you mount it, so register parsers before the routes that depend on them. Static middleware is usually mounted early too, so asset requests short-circuit before hitting route logic.
app.use(express.json()); // parse bodies first
app.use(express.urlencoded({ extended: true }));
app.use(express.static("public")); // then serve assets
app.use("/api", apiRouter); // then app routes
Best Practices
- Mount only the parsers you actually need — skip
express.urlencoded()if your API is JSON-only. - Set an explicit
limiton body parsers to protect against oversized payloads and denial-of-service. - Always pass
extendedtoexpress.urlencoded()explicitly so behavior is identical across Express 4 and 5. - Serve static files with an absolute path and a sensible
maxAgefor caching. - Register parsers and static middleware before your routers so
req.bodyand asset paths resolve correctly. - Reach for the standalone
body-parserpackage only for advanced cases (e.g.textorrawparsers); ordinary JSON and form parsing is covered by core.