Suggest an editImprove this articleRefine the answer for “How does body parsing work in Express.js?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Body parsing in Express.js** converts a raw HTTP byte stream into a JavaScript object on `req.body`. Add `express.json()` for JSON APIs and `express.urlencoded({ extended: true })` for HTML forms, before your route definitions. ```js app.use(express.json()); app.post('/users', (req, res) => { console.log(req.body); // { name: 'Alice' } }); ``` **Key point:** without a parser, `req.body` is always `undefined`.Shown above the full answer for quick recall.Answer (EN)Image**Body parsing in Express.js** reads the raw byte stream from an HTTP request body and converts it into a JavaScript object on `req.body`, based on the `Content-Type` header. ## Theory ### TL;DR - Without a parser, `req.body` is always `undefined`, even if the client sent data - `express.json()` handles `application/json`; `express.urlencoded()` handles HTML forms - Register parsers before route definitions, not after - streams are one-pass - Default size limit is 1mb; adjust with `{ limit: '10kb' }` per route - `multipart/form-data` (file uploads) needs `multer`, not the built-in parsers ### Quick example ```js const express = require('express'); const app = express(); // Parsers BEFORE routes app.use(express.json()); // application/json app.use(express.urlencoded({ extended: true })); // HTML forms app.post('/user', (req, res) => { console.log(req.body); // { name: 'Alice', age: 30 } res.json(req.body); }); app.listen(3000); // curl -X POST -H "Content-Type: application/json" \ // -d '{"name":"Alice","age":30}' http://localhost:3000/user ``` The middleware runs before your route handler. By the time your code reads `req.body`, the bytes are already parsed. ### How it works internally Node.js exposes the request body as a readable stream via `req.on('data')`. The `express.json()` middleware buffers incoming chunks up to the size limit (1mb by default), then checks the `Content-Type` header. If it matches `application/json`, it calls `JSON.parse()` on the concatenated UTF-8 string and attaches the result to `req.body`. If the JSON is malformed, it throws a 400 error before your route runs. `express.urlencoded()` does the same but uses the `qs` library (when `extended: true`) or Node's built-in `querystring` module (when `extended: false`) to parse `name=Alice&age=30` style strings. Before Express 4.16, you needed the separate `body-parser` npm package. It ships built-in now, though `body-parser` is still importable for custom configurations. ### When to use each parser - JSON API endpoints (`/users`, `/products`) - `express.json()` - HTML form submissions (login, register pages) - `express.urlencoded({ extended: true })` - File uploads (`<form enctype="multipart/form-data">`) - `multer`, not the built-ins - Stripe webhooks needing raw bytes for signature verification - `express.raw({ type: '*/*' })` - GET routes, static files - skip parsers entirely to save memory ### The `extended` option `extended: false` uses Node's `querystring` module, which only handles flat key-value pairs. `extended: true` uses the `qs` library, which parses nested objects and arrays: ```js // extended: false // Input: user[name]=Alice&user[age]=30 // Output: { 'user[name]': 'Alice', 'user[age]': '30' } <- flat strings // extended: true // Input: user[name]=Alice&user[age]=30 // Output: { user: { name: 'Alice', age: '30' } } <- proper nesting ``` For most real apps, `extended: true` is what you want. ### Common mistakes **Mistake: parser registered after the route** ```js app.post('/user', handler); // req.body = undefined here app.use(express.json()); // too late - stream already consumed ``` Streams are read once. The route runs first and the parser never sees the data. The parser order bug catches almost every Express developer at least once - you add logging everywhere, the network tab looks fine, but `req.body` stays `undefined`, and then you spot the `app.use()` call sitting three lines below the route. Move it to the top. **Mistake: missing Content-Type header on the client** ```js // This sends data without telling the parser what format it is: fetch('/user', { method: 'POST', body: JSON.stringify(data) }); // Fix: fetch('/user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); ``` The parser checks the header before touching the stream. No header match means `req.body` stays `{}` or `undefined`. **Mistake: using built-in parsers for file uploads** `<form enctype="multipart/form-data">` sends `multipart/form-data`. The built-in parsers skip this format entirely. `req.body` will be `{}`. You need `multer` for file uploads. **Mistake: no size limit on untrusted input** ```js app.use(express.json()); // 1mb default - fine for most APIs // For public-facing auth endpoints, be explicit: app.post('/login', express.json({ limit: '10kb' }), handler); ``` A client sending a 500mb payload buffers everything in memory before the parser rejects it. That is a straightforward denial-of-service vector. Set explicit limits for routes that accept untrusted data. **Mistake: `extended: false` with nested form data** ```js app.use(express.urlencoded({ extended: false })); // Form: user[profile][name]=Alice // req.body = { 'user[profile][name]': 'Alice' } <- keys become strings, no nesting ``` Switch to `extended: true` if your forms send nested structures. ### Real-world usage - React/Next.js API routes: `express.json()` for REST endpoints receiving `fetch` with `JSON.stringify` - Login and register pages: `express.urlencoded({ extended: true })` for browser form submits - Stripe webhooks: `express.raw({ type: '*/*' })` so you can verify the HMAC signature against the exact bytes before any parsing - File upload endpoints: `multer` with `diskStorage` or `memoryStorage` depending on whether you write to disk or stream to S3 - Prisma/Drizzle mutations: parse the body, validate with Zod or Joi, then pass to the ORM ### Follow-up questions **Q:** What happens if you register `express.json()` twice? **A:** Both parsers run in sequence. The first one buffers and parses the stream. The second one gets an empty stream, does nothing useful, but still spends CPU on the attempt. Register it once globally. **Q:** What is the difference between `express.json()` and `express.raw()`? **A:** `express.json()` parses the body and gives you a JS object. `express.raw()` gives you the raw `Buffer` with no parsing. Stripe webhook verification requires `express.raw()` because the HMAC signature is computed over the exact bytes, not the parsed object. **Q:** Why does `req.body` stay `undefined` even after adding `express.json()`? **A:** Three common causes: parser registered after the route, client not sending `Content-Type: application/json`, or the body is `multipart/form-data` which the parser skips. **Q:** How did body parsing work before Express 4.16? **A:** You installed `body-parser` as a separate package and called `app.use(bodyParser.json())`. Express 4.16 bundled it. The `body-parser` package still works and is still useful for per-route options or older setups. **Q:** What are the memory implications of 1000 concurrent 1mb JSON requests on a 4GB server? **A:** Each request buffers roughly 1mb of raw bytes plus the parsed object overhead - about 2mb total. 1000 concurrent requests hit roughly 2GB peak. For large payloads, process with streaming (`req.on('data')`) or use worker clusters. Monitor with `process.memoryUsage()` or `clinic.js`. ## Examples ### Basic: JSON API endpoint ```js const express = require('express'); const app = express(); app.use(express.json()); app.post('/register', (req, res) => { const { email, password } = req.body; // { email: 'user@example.com', password: 'secret' } console.log('Registering:', email); res.status(201).json({ message: 'User created' }); }); app.listen(3000); ``` `express.json()` runs before the route handler. By the time your code reads `req.body`, it is already a plain JS object. ### Intermediate: Route-specific parsing with different limits ```js const express = require('express'); const app = express(); // Tight limit for auth endpoints app.post('/login', express.json({ limit: '10kb' }), (req, res) => { const { email, password } = req.body; res.json({ token: 'abc123' }); } ); // Stripe webhook needs raw bytes, not a parsed object app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; // req.body is a Buffer here - needed for HMAC verification console.log(Buffer.isBuffer(req.body)); // true res.sendStatus(200); } ); app.listen(3000); ``` Route-specific parsers let you apply different strategies per endpoint without one global config affecting everything. ### Advanced: File upload with multer ```js const express = require('express'); const multer = require('multer'); const app = express(); app.use(express.json()); const upload = multer({ dest: 'uploads/', limits: { fileSize: 5 * 1024 * 1024 } // 5MB }); app.post('/avatar', upload.single('avatar'), (req, res) => { // req.file -> { fieldname, originalname, mimetype, size, path } // req.body -> other text fields from the same form console.log(req.file.originalname); // 'profile.jpg' res.json({ filename: req.file.filename }); }); app.listen(3000); ``` `multer` handles `multipart/form-data` that the built-in parsers skip. Text fields from the same form still land on `req.body`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.