Node.js 32 🟢 Request Body Parsing (JSON, Form Data, URL-Encoded)
A request body is a stream of bytes. The application needs the parsed data: the JSON object, the form fields, the uploaded file. Express does not parse the body by default. The req.body is undefined until a middleware reads the stream, decodes the bytes, and assigns the result. The parsing middleware—express.json, express.urlencoded, express.raw, express.text, and the third-party multer for multipart—is the mechanism that turns the stream into the data.
Key point: The body parser must be registered before the routes that read req.body. The parser reads the entire body into memory, so it should have a size limit. express.json parses application/json. express.urlencoded parses application/x-www-form-urlencoded (HTML forms). express.raw parses the body as a Buffer. express.text parses the body as a string. multer parses multipart/form-data (file uploads). The parsers are content-type aware; they only parse the bodies that match their type.
Why body parsing matters
The stream problem. The request body arrives as a stream. Reading it requires the data and end events, the chunk concatenation, and the decoding. The body parser does this once and caches the result in req.body. Without the parser, every route would repeat the stream handling, and the body would be consumed twice if two routes read it.
The content-type problem. The body’s format is determined by the Content-Type header. application/json is JSON. application/x-www-form-urlencoded is the form format. multipart/form-data is the file upload format. text/plain is the raw text. The parser reads the header and parses the body only if the type matches. A JSON parser does not parse a form body, and a form parser does not parse a JSON body.
The size problem. The body is read into memory. A large body—a file upload, a large JSON payload—consumes memory. The parser has a limit option, and the default is 100kb for the JSON and URL-encoded parsers. A body larger than the limit produces a 413 Payload Too Large error.
The security problem. The body parser is the first place the untrusted data enters the application. The JSON parser rejects the malformed JSON. The URL-encoded parser rejects the malformed pairs. The extended option controls whether the qs library is used, and the library has a depth limit to prevent the prototype pollution attacks.
The multipart problem. The multipart/form-data format is not handled by the built-in parsers. It is a binary format with the boundaries and the file parts. The multer middleware handles it: it parses the fields into req.body and the files into req.file or req.files.
a. express.json and express.urlencoded
The express.json middleware parses the JSON bodies. It reads the Content-Type: application/json requests and populates req.body.
app.use(express.json())
The options configure the parser:
| Option | Default | Purpose |
|---|---|---|
limit | 100kb | Maximum body size |
strict | true | Only accept arrays and objects |
type | application/json | The content type to match |
reviver | null | The JSON.parse reviver |
The limit option accepts a number of bytes or a string like '1mb'. The strict option rejects the top-level values that are not arrays or objects; with strict: false, the parser accepts 42, "text", and null.
app.use(express.json({ limit: '1mb' }))
The express.urlencoded middleware parses the URL-encoded bodies. It reads the Content-Type: application/x-www-form-urlencoded requests and populates req.body.
app.use(express.urlencoded({ extended: true }))
The extended option selects the parsing library. extended: true uses the qs library, which supports the nested objects and arrays. extended: false uses the querystring library, which does not.
// extended: true (qs library)
// "user[name]=alice&user[age]=30"
// → { user: { name: 'alice', age: 30 } }
// extended: false (querystring library)
// "user[name]=alice&user[age]=30"
// → { 'user[name]': 'alice', 'user[age]': '30' }
The qs library has a parameterLimit option that limits the number of parameters, and a depth option that limits the nesting depth. The defaults are 1000 and 5.
The express.json and express.urlencoded can be registered together.
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
The first parses the JSON bodies, and the second parses the form bodies. The parser that does not match the content type passes the request to the next middleware without modifying req.body.
b. express.raw and express.text
The express.raw middleware parses the body as a Buffer. It is for the binary content, the webhooks with the signature verification, and the cases where the raw bytes are needed.
app.use(express.raw({ type: 'application/octet-stream' }))
The type option specifies the content type to match. The express.raw has the limit and the type options.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature']
const payload = req.body // Buffer
if (!verifySignature(payload, signature)) {
return res.status(401).send('Invalid signature')
}
const data = JSON.parse(payload.toString())
res.json({ received: true })
})
The raw parser is registered for the webhook route. The req.body is the Buffer with the raw bytes. The signature verification uses the raw bytes, which is why the JSON parser is not used: the JSON parser would lose the exact byte sequence that the signature covers.
The express.text middleware parses the body as a string.
app.use(express.text({ type: 'text/plain' }))
The req.body is the string with the decoded content. The express.text has the limit, type, and defaultCharset options.
app.post('/text', express.text(), (req, res) => {
res.send(`Received: ${req.body}`)
})
The parser is for the text content, the CSV bodies, and the plain text payloads.
The express.raw and express.text are registered on the specific routes where they apply, rather than globally, because the default content types are not the ones the application uses for the normal requests.
c. The multipart parsing with multer
The multipart/form-data format is not handled by the built-in parsers. It is the format that the HTML forms with the enctype="multipart/form-data" use for the file uploads. The multer middleware parses it.
npm install multer
The multer is configured with the storage engine and the limits.
const multer = require('multer')
const upload = multer({
dest: 'uploads/', // the destination directory
limits: { fileSize: 5 * 1024 * 1024 } // 5 MB
})
The dest is the directory where the files are stored. The limits.fileSize is the maximum file size. The limits option also includes files, fields, and parts.
The multer is registered as the middleware on the routes that accept the uploads.
app.post('/upload', upload.single('avatar'), (req, res) => {
console.log(req.file) // the file metadata
console.log(req.body) // the text fields
res.json({ file: req.file })
})
The upload.single('avatar') accepts a single file with the field name avatar. The file is in req.file. The text fields are in req.body.
The upload.array('photos', 5) accepts the multiple files with the field name photos, up to 5.
app.post('/photos', upload.array('photos', 5), (req, res) => {
res.json({ files: req.files })
})
The req.files is the array of the file objects.
The upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }]) accepts the multiple fields.
const fields = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 8 }
])
app.post('/profile', fields, (req, res) => {
res.json({ avatar: req.files.avatar, gallery: req.files.gallery })
})
The req.files is the object with the field names as the keys and the file arrays as the values.
The multer storage engine can be the disk storage or the memory storage. The diskStorage allows the control of the destination and the filename.
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/')
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`)
}
})
const upload = multer({ storage })
The memoryStorage stores the file as a Buffer in req.file.buffer.
const upload = multer({ storage: multer.memoryStorage() })
app.post('/upload', upload.single('file'), (req, res) => {
const buffer = req.file.buffer // the file content
res.json({ size: buffer.length })
})
The memory storage is for the cases where the file is processed in memory and not saved to the disk.
The multer errors are the MulterError instances. The error handler checks the err.code for the specific cases: LIMIT_FILE_SIZE, LIMIT_FILE_COUNT, and the rest.
Complete Example Session
// ============================================
// PART 1: JSON PARSER
// ============================================
app.use(express.json())
app.post('/api/users', (req, res) => {
res.json({ received: req.body })
})
// ============================================
// PART 2: JSON PARSER WITH LIMIT
// ============================================
app.use(express.json({ limit: '1mb' }))
// ============================================
// PART 3: URL-ENCODED PARSER
// ============================================
app.use(express.urlencoded({ extended: true }))
app.post('/form', (req, res) => {
res.json({ received: req.body })
})
// ============================================
// PART 4: EXTENDED FALSE
// ============================================
app.use(express.urlencoded({ extended: false }))
// "user[name]=alice" → { 'user[name]': 'alice' }
// ============================================
// PART 5: RAW PARSER
// ============================================
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature']
const payload = req.body // Buffer
res.json({ size: payload.length })
})
// ============================================
// PART 6: TEXT PARSER
// ============================================
app.post('/text', express.text({ type: 'text/plain' }), (req, res) => {
res.send(`Received: ${req.body}`)
})
// ============================================
// PART 7: MULTER SETUP
// ============================================
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
// ============================================
// PART 8: SINGLE FILE UPLOAD
// ============================================
app.post('/upload', upload.single('avatar'), (req, res) => {
res.json({ file: req.file, body: req.body })
})
// ============================================
// PART 9: MULTIPLE FILE UPLOAD
// ============================================
app.post('/photos', upload.array('photos', 5), (req, res) => {
res.json({ files: req.files })
})
// ============================================
// PART 10: MULTER ERROR HANDLING
// ============================================
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({ error: 'File too large' })
}
return res.status(400).json({ error: err.message })
}
next(err)
})
The ten parts covered the JSON parser, the JSON parser with the limit, the URL-encoded parser, extended: false, the raw parser, the text parser, the Multer setup, the single file upload, the multiple file upload, and the Multer error handling.
Quick Reference
Body Parsers
| Parser | Content Type | req.body |
|---|---|---|
express.json | application/json | Parsed object |
express.urlencoded | application/x-www-form-urlencoded | Parsed object |
express.raw | Configurable | Buffer |
express.text | text/plain | String |
multer | multipart/form-data | req.body + req.file(s) |
Parser Options
| Option | Default | Purpose |
|---|---|---|
limit | 100kb | Maximum body size |
type | Content-type specific | Which types to parse |
strict | true | JSON only arrays/objects |
extended | false | qs vs querystring |
parameterLimit | 1000 | Max parameters |
depth | 5 | Max nesting depth |
Multer Methods
| Method | Accepts |
|---|---|
upload.single(field) | One file |
upload.array(field, max) | Multiple files, same field |
upload.fields([...]) | Multiple fields |
upload.none() | Text fields only |
Multer Storage
| Storage | req.file |
|---|---|
diskStorage | File metadata with path |
memoryStorage | Buffer in req.file.buffer |
Multer Errors
| Code | Meaning |
|---|---|
LIMIT_FILE_SIZE | File too large |
LIMIT_FILE_COUNT | Too many files |
LIMIT_UNEXPECTED_FILE | Unexpected field |
LIMIT_PART_COUNT | Too many parts |
Best Practices
✅ Do This:
// Register the body parser before the routes
app.use(express.json()) // ✅
// Set a size limit
app.use(express.json({ limit: '1mb' })) // ✅
// Use extended: true for the nested form data
app.use(express.urlencoded({ extended: true })) // ✅
// Use express.raw for the webhook signature verification
app.post('/webhook', express.raw({ type: 'application/json' }))// ✅
// Use multer for the multipart/form-data
app.post('/upload', upload.single('file')) // ✅
// Handle the multer errors
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) { } // ✅
})
❌ Don’t Do This:
// Don't read req.body without the parser
app.post('/api', (req, res) => {
res.json(req.body) // undefined // ❌
})
// Don't use the default limit for the file uploads
app.use(express.json()) // 100kb is too small for some // ⚠️
// Don't use express.json for the multipart/form-data
app.use(express.json()) // multipart is not JSON // ❌
// Don't forget the multer for the file uploads
app.post('/upload', (req, res) => {
req.file // undefined without multer // ❌
})
// Don't trust the Content-Type header
// The parser checks the type, but the client can lie // ⚠️
// Don't parse the body twice
app.use(express.json())
app.use(express.text()) // the body is already consumed // ⚠️
// Don't forget the multer error handling
// The errors are not caught by the default handler // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
req.body undefined | No parser | Register the parser |
413 Payload Too Large | Body exceeds limit | Increase limit |
| Multipart not parsed | No multer | Install and register |
req.file undefined | Wrong field name | Check the upload.single argument |
| Nested form data not parsed | extended: false | Use extended: true |
| Multer error unhandled | No error handler | Catch MulterError |
| Body consumed twice | Two parsers | One parser per content type |
Real-World Examples
1. JSON API
app.use(express.json({ limit: '1mb' }))
app.post('/api/users', (req, res) => {
res.status(201).json(req.body)
})
2. HTML Form
app.use(express.urlencoded({ extended: true }))
app.post('/login', (req, res) => {
const { email, password } = req.body
})
3. File Upload
const upload = multer({ dest: 'uploads/' })
app.post('/avatar', upload.single('avatar'), (req, res) => {
res.json({ file: req.file })
})
4. Multiple Files
app.post('/photos', upload.array('photos', 10), (req, res) => {
res.json({ count: req.files.length })
})
5. Webhook Signature
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-signature']
const valid = verify(req.body, sig)
res.json({ valid })
})
6. Plain Text
app.post('/text', express.text(), (req, res) => {
res.send(req.body.toUpperCase())
})
7. Memory Storage
const upload = multer({ storage: multer.memoryStorage() })
app.post('/process', upload.single('file'), (req, res) => {
const buffer = req.file.buffer
})
8. Disk Storage with Custom Name
const storage = multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`)
})
9. Mixed Fields
const fields = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 8 }
])
app.post('/profile', fields, (req, res) => {
res.json({ avatar: req.files.avatar, gallery: req.files.gallery })
})
10. Multer Error Handler
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({ error: 'File too large' })
}
}
next(err)
})
Visual
Body Parser Flow
┌─────────────────────────────────────────────────────────────┐
│ REQUEST │
│ │ │
│ ▼ │
│ Content-Type: application/json │
│ │ │
│ ▼ │
│ express.json() │
│ │ │
│ ├── Type matches ──▶ Parse ──▶ req.body = {...} │
│ │ │
│ └── Type differs ──▶ Pass ──▶ req.body unchanged │
│ │
│ The parser is content-type aware. │
│ │
└─────────────────────────────────────────────────────────────┘
Multipart Upload
┌─────────────────────────────────────────────────────────────┐
│ CLIENT │
│ │ │
│ ▼ multipart/form-data │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ --boundary │ │
│ │ Content-Disposition: form-data; name="avatar"; │ │
│ │ filename="photo.jpg" │ │
│ │ Content-Type: image/jpeg │ │
│ │ │ │
│ │ <binary data> │ │
│ │ --boundary │ │
│ │ Content-Disposition: form-data; name="name" │ │
│ │ │ │
│ │ Alice │ │
│ │ --boundary-- │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ multer │
│ │ │
│ ├── req.body = { name: 'Alice' } │
│ └── req.file = { path, size, mimetype, ... } │
│ │
└─────────────────────────────────────────────────────────────┘
Parser Selection
┌─────────────────────────────────────────────────────────────┐
│ application/json → express.json │
│ application/x-www-form- → express.urlencoded │
│ urlencoded │
│ multipart/form-data → multer │
│ text/plain → express.text │
│ application/octet-stream → express.raw │
│ │
│ The parser is selected by the Content-Type header. │
│ │
└─────────────────────────────────────────────────────────────┘
Multer File Object
┌─────────────────────────────────────────────────────────────┐
│ req.file │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ fieldname: 'avatar' │ │
│ │ originalname: 'photo.jpg' │ │
│ │ encoding: '7bit' │ │
│ │ mimetype: 'image/jpeg' │ │
│ │ size: 204800 │ │
│ │ destination: 'uploads/' │ │
│ │ filename: 'a1b2c3d4e5f6' │ │
│ │ path: 'uploads/a1b2c3d4e5f6' │ │
│ │ buffer: <Buffer ...> (memory storage only) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| JSON | express.json() |
| URL-encoded | express.urlencoded() |
| Raw | express.raw() |
| Text | express.text() |
| Multipart | multer |
| Default limit | 100kb |
extended: true | qs library, nested objects |
extended: false | querystring, flat |
| Multer single | upload.single('field') |
| Multer array | upload.array('field', max) |
| Multer fields | upload.fields([...]) |
| Memory storage | req.file.buffer |
| Disk storage | req.file.path |
Key takeaways:
- The body parser must be registered before the routes. The
req.bodyisundefineduntil a parser reads the stream and assigns the result. The parser is registered withapp.use()and runs in the order of the registration . - The parsers are content-type aware.
express.jsonparsesapplication/json.express.urlencodedparsesapplication/x-www-form-urlencoded.multerparsesmultipart/form-data. The parser that does not match the type passes the request without modifyingreq.body. - The
limitoption controls the body size. The default is100kbfor the JSON and URL-encoded parsers. A body larger than the limit produces a413 Payload Too Largeerror . - The
extendedoption selects the parsing library.extended: trueusesqs, which supports the nested objects and arrays.extended: falseusesquerystring, which does not . - The
multerhandles the multipart form data. Theupload.singleaccepts one file,upload.arrayaccepts multiple files with the same field, andupload.fieldsaccepts multiple fields. The file is inreq.fileorreq.files, and the text fields are inreq.body. - The
multerstorage engine controls where the file goes. ThediskStoragewrites the file to the disk and provides the path. ThememoryStoragekeeps the file in theBufferinreq.file.buffer. - The
multererrors need the explicit handling. TheMulterErroris not caught by the default error handler. The error handler checks theerr.codeforLIMIT_FILE_SIZEand the other codes .
Remember: The request body is a stream of bytes, and the body parser is the middleware that turns it into the data. The parser is selected by the content type, and the parser must run before the routes that read the body. The JSON parser for the JSON APIs, the URL-encoded parser for the HTML forms, the raw parser for the webhooks, the text parser for the plain text, and the Multer for the file uploads. Set the size limit, handle the errors, and register the parsers in the right order. The req.body is the parsed data, and the parser is the mechanism that produces it.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!