Skip to content
Express.js ex middleware 4 min read

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:

MiddlewarePurposeReplaces
express.json()Parses application/json request bodies into req.bodybody-parser.json()
express.urlencoded()Parses application/x-www-form-urlencoded bodies into req.bodybody-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.body stays undefined.

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:

OptionDefaultDescription
limit"100kb"Maximum body size; oversized requests get a 413
stricttrueOnly accept arrays and objects as the top-level JSON value
type"application/json"Content types to parse (string, array, or function)
reviverundefinedPassed 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: false uses the querystring library — values are always strings, no nested objects.
  • extended: true uses the qs library, 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, extended defaults to false. 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 limit on body parsers to protect against oversized payloads and denial-of-service.
  • Always pass extended to express.urlencoded() explicitly so behavior is identical across Express 4 and 5.
  • Serve static files with an absolute path and a sensible maxAge for caching.
  • Register parsers and static middleware before your routers so req.body and asset paths resolve correctly.
  • Reach for the standalone body-parser package only for advanced cases (e.g. text or raw parsers); ordinary JSON and form parsing is covered by core.
Last updated June 14, 2026
Was this helpful?