express.json & body-parser
When a client sends a POST, PUT, or PATCH request, the payload arrives as a raw stream of bytes — Express does not decode it for you. Body-parsing middleware reads that stream, parses it according to the Content-Type header, and attaches the result to req.body. Modern Express ships these parsers built in as express.json() and express.urlencoded(), so the once-ubiquitous standalone body-parser package is no longer needed for the common cases.
A short history: body-parser is now built in
For years you installed the body-parser package separately and wrote bodyParser.json(). Since Express 4.16, the same parsers are re-exported directly from Express, so you can drop the dependency. The two forms are functionally identical:
// Old style — extra dependency
const bodyParser = require("body-parser");
app.use(bodyParser.json());
// Modern style — built in, no install needed
app.use(express.json());
You only still need the standalone package for exotic parsers it exposes that Express does not re-export. For everyday JSON and form data, prefer the built-ins.
Parsing JSON bodies
express.json() returns middleware that parses requests whose Content-Type is application/json. After it runs, the decoded object lives on req.body. Mount it once, before your routes, and every handler downstream sees the parsed body.
const express = require("express");
const app = express();
app.use(express.json());
app.post("/api/users", async (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: "name and email are required" });
}
const user = await createUser({ name, email });
res.status(201).json(user);
});
app.listen(3000, () => console.log("Listening on http://localhost:3000"));
Send a JSON request and the parsed fields are available immediately:
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Ada","email":"[email protected]"}'
Output:
HTTP/1.1 201 Created
Content-Type: application/json
{"id":42,"name":"Ada","email":"[email protected]"}
If the
Content-Typeheader is missing or is notapplication/json,express.json()skips the request and leavesreq.bodyasundefined(or{}in Express 5). Always destructure defensively or validate before use.
Parsing URL-encoded form bodies
HTML forms submitted without JavaScript send application/x-www-form-urlencoded data — key=value pairs joined by &. express.urlencoded() decodes those into req.body. The extended option chooses the parsing library: extended: true uses qs and supports nested objects and arrays; extended: false uses the built-in querystring and only handles flat string values.
app.use(express.urlencoded({ extended: true }));
app.post("/submit", (req, res) => {
// body: "user[name]=Ada&tags[]=admin&tags[]=staff"
console.log(req.body);
res.redirect("/thanks");
});
Output:
{ user: { name: 'Ada' }, tags: [ 'admin', 'staff' ] }
With extended: false, the same payload would yield flat keys like { 'user[name]': 'Ada' }, so reach for extended: true whenever your forms use bracket notation.
Common options
Both parsers accept an options object. The most useful settings control payload size, strictness, and which content types are matched.
| Option | Applies to | Default | Purpose |
|---|---|---|---|
limit | both | "100kb" | Maximum body size; larger requests get 413 Payload Too Large |
strict | json | true | When true, only accept arrays and objects (reject bare "text"/42) |
type | both | application/json / application/x-www-form-urlencoded | Override which Content-Type triggers the parser |
extended | urlencoded | (must set) | true for nested objects via qs, false for flat values |
inflate | both | true | Handle gzip/deflate-compressed request bodies |
verify | both | — | Callback (req, res, buf, encoding) to inspect the raw buffer |
app.use(
express.json({
limit: "1mb", // accept larger payloads, e.g. rich documents
strict: true, // reject top-level primitives like 42 or "hi"
type: "application/*+json", // also parse vendor JSON like application/vnd.api+json
})
);
The limit guard is a cheap but important defense — without it a client could stream a huge body and exhaust memory. The verify hook is handy for webhook signature checks, since it hands you the exact raw bytes before parsing (for example, verifying a Stripe or GitHub HMAC).
Handling parse errors
If a client sends malformed JSON, express.json() calls next(err) with a SyntaxError carrying status: 400 and a body property. Catch it in an error-handling middleware to return a clean response instead of leaking a stack trace.
app.use((err, req, res, next) => {
if (err.type === "entity.parse.failed") {
return res.status(400).json({ error: "Invalid JSON in request body" });
}
if (err.type === "entity.too.large") {
return res.status(413).json({ error: "Request body too large" });
}
next(err);
});
Output:
HTTP/1.1 400 Bad Request
Content-Type: application/json
{"error":"Invalid JSON in request body"}
Best Practices
- Use the built-in
express.json()andexpress.urlencoded()rather than installingbody-parser— they are the same code, one less dependency. - Always pass
extended: truetoexpress.urlencoded()when forms use nested or array fields; otherwise values stay flat. - Set an explicit
limitsized to your real payloads to cap memory use and reject abusive requests with413. - Mount parsers before your routes but consider scoping them per-router so endpoints that take no body (or raw uploads) are not forced through JSON parsing.
- Add an error-handling middleware that inspects
err.type(entity.parse.failed,entity.too.large) to return clean400/413responses. - Never trust
req.bodyblindly — validate and sanitize it with a schema or validator before persisting or acting on it.