| |

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:

OptionDefaultPurpose
limit100kbMaximum body size
stricttrueOnly accept arrays and objects
typeapplication/jsonThe content type to match
revivernullThe 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

ParserContent Typereq.body
express.jsonapplication/jsonParsed object
express.urlencodedapplication/x-www-form-urlencodedParsed object
express.rawConfigurableBuffer
express.texttext/plainString
multermultipart/form-datareq.body + req.file(s)

Parser Options

OptionDefaultPurpose
limit100kbMaximum body size
typeContent-type specificWhich types to parse
stricttrueJSON only arrays/objects
extendedfalseqs vs querystring
parameterLimit1000Max parameters
depth5Max nesting depth

Multer Methods

MethodAccepts
upload.single(field)One file
upload.array(field, max)Multiple files, same field
upload.fields([...])Multiple fields
upload.none()Text fields only

Multer Storage

Storagereq.file
diskStorageFile metadata with path
memoryStorageBuffer in req.file.buffer

Multer Errors

CodeMeaning
LIMIT_FILE_SIZEFile too large
LIMIT_FILE_COUNTToo many files
LIMIT_UNEXPECTED_FILEUnexpected field
LIMIT_PART_COUNTToo 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

PitfallWhy It HappensFix
req.body undefinedNo parserRegister the parser
413 Payload Too LargeBody exceeds limitIncrease limit
Multipart not parsedNo multerInstall and register
req.file undefinedWrong field nameCheck the upload.single argument
Nested form data not parsedextended: falseUse extended: true
Multer error unhandledNo error handlerCatch MulterError
Body consumed twiceTwo parsersOne 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

ItemValue
JSONexpress.json()
URL-encodedexpress.urlencoded()
Rawexpress.raw()
Textexpress.text()
Multipartmulter
Default limit100kb
extended: trueqs library, nested objects
extended: falsequerystring, flat
Multer singleupload.single('field')
Multer arrayupload.array('field', max)
Multer fieldsupload.fields([...])
Memory storagereq.file.buffer
Disk storagereq.file.path

Key takeaways:

  • The body parser must be registered before the routes. The req.body is undefined until a parser reads the stream and assigns the result. The parser is registered with app.use() and runs in the order of the registration .
  • The parsers are content-type aware. express.json parses application/json. express.urlencoded parses application/x-www-form-urlencoded. multer parses multipart/form-data. The parser that does not match the type passes the request without modifying req.body .
  • The limit option controls the body size. 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 extended option selects the parsing library. extended: true uses qs, which supports the nested objects and arrays. extended: false uses querystring, which does not .
  • The multer handles the multipart form data. The upload.single accepts one file, upload.array accepts multiple files with the same field, and upload.fields accepts multiple fields. The file is in req.file or req.files, and the text fields are in req.body .
  • The multer storage engine controls where the file goes. The diskStorage writes the file to the disk and provides the path. The memoryStorage keeps the file in the Buffer in req.file.buffer .
  • The multer errors need the explicit handling. The MulterError is not caught by the default error handler. The error handler checks the err.code for LIMIT_FILE_SIZE and 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!