Parsing JSON and form bodies

Express · Essentials · 10 min

POST, PUT and PATCH endpoints are useless until you can read what the client sent.

Express doesn't parse request bodies by default — the body is still a raw stream. Add the built-in body-parsing middleware:

  • app.use(express.json()) — for Content-Type: application/json. Parsed data appears on req.body.
  • app.use(express.urlencoded()) — for HTML form posts (application/x-www-form-urlencoded).
  • express.text() and express.raw() for plain text and binary (raw gives a Buffer — useful for verifying webhook signatures).

Each parser only acts when the request's Content-Type matches its type option. In Express 5, if no parser ran, `req.body` is `undefined` (Express 4 gave {}), so code like req.body.name throws on a request sent without a JSON content type. Destructure defensively or validate first.

Options worth knowing:

  • limit — maximum body size, default "100kb". Larger bodies get a 413 error. Raise it only where needed, ideally on a specific route: app.post("/import", express.json({ limit: "5mb" }), handler).
  • strict (json, default true) — only accept objects and arrays at the top level.
  • extended (urlencoded) — Express 5 defaults to false, using the simple querystring parser; set true for nested a[b]=c syntax via the qs library.

Malformed JSON produces an error with status 400 and type: "entity.parse.failed"; it flows to your error handler, so respond with a helpful 400 there instead of a stack trace.

req.body is untrusted: it can be any JSON shape, including a number, extra fields, or __proto__ keys. Validate it (a later lesson) before using it. File uploads (multipart/form-data) need a different parser such as multer.

Example

import express from "express";
const app = express();

app.use(express.json());                              // application/json -> req.body
app.use(express.urlencoded());                        // HTML forms (extended: false in v5)

app.post("/messages", (req, res) => {
  const text = req.body?.text;                        // body is undefined if not parsed
  if (typeof text !== "string" || text.length === 0) {
    return res.status(400).json({ error: "text is required" });
  }
  res.status(201).json({ text, length: text.length });
});

app.post("/import", express.json({ limit: "5mb" }), (req, res) => {
  res.json({ items: Array.isArray(req.body) ? req.body.length : 0 });
});

app.use((err, req, res, next) => {                    // malformed JSON, too large, ...
  if (err.type === "entity.parse.failed") return res.status(400).json({ error: "invalid JSON" });
  if (err.type === "entity.too.large") return res.status(413).json({ error: "body too large" });
  next(err);
});

app.listen(3000);

Remember

  • Add express.json() / express.urlencoded(); nothing is parsed by default
  • Express 5: req.body is undefined when no parser ran
  • Default limit is 100kb; raise it per route when needed
  • Malformed JSON becomes a 400-status error for your error handler

Watch out for

  • Testing with curl but forgetting -H 'Content-Type: application/json', so req.body is undefined.

Check yourself

  1. A client POSTs valid JSON with Content-Type: text/plain to an app using express.json(). What is req.body in Express 5?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: expressjs.com/en/api/express