Read POST request data in Node.js

Parse a bounded JSON request body with native HTTP or Express, handle malformed input, and distinguish JSON parsing from validation.

Best practices
Illustration accompanying a Node.js development guide

With Node's built-in HTTP server, the request body arrives as a stream. Collect a bounded amount of data, decode it, and parse it according to the content type. In Express, use a body parser before the route that reads req.body.

Read JSON with native HTTP

This example accepts a small JSON object at POST /api/user. Save it as post.cjs:

const http = require('node:http');
const MAX_BYTES = 16 * 1024;

const server = http.createServer((req, res) => {
  function reply(status, value) {
    res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
    res.end(JSON.stringify(value));
  }
  if (req.method !== 'POST' || req.url !== '/api/user') {
    req.resume();
    return reply(404, { error: 'Not found' });
  }
  const type = (req.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
  if (type !== 'application/json') {
    req.resume();
    return reply(415, { error: 'Expected application/json' });
  }
  const chunks = [];
  let size = 0;
  let rejected = false;
  req.on('data', (chunk) => {
    if (rejected) return;
    size += chunk.length;
    if (size > MAX_BYTES) {
      rejected = true;
      chunks.length = 0;
      res.setHeader('Connection', 'close');
      reply(413, { error: 'Body too large' });
      return;
    }
    chunks.push(chunk);
  });
  req.on('error', () => {
    rejected = true;
    chunks.length = 0;
    res.destroy();
  });
  req.on('end', () => {
    if (rejected) return;
    let body;
    try {
      body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
    } catch {
      return reply(400, { error: 'Invalid JSON' });
    }
    if (!body || typeof body !== 'object' || Array.isArray(body) ||
        typeof body.name !== 'string' || typeof body.email !== 'string') {
      return reply(400, { error: 'name and email must be strings' });
    }
    reply(200, { accepted: true });
  });
});
server.requestTimeout = 30000;
server.listen(3000, '127.0.0.1');

The limit counts bytes, not JavaScript characters. Concatenating Buffers before decoding avoids corrupting a multibyte character split across chunks. After rejecting a large body, the handler stops retaining chunks; timeouts and connection policy still matter because memory limits alone do not limit all network work.

The field checks only illustrate validation. A real endpoint should define acceptable lengths and values, authentication, and the behavior of its downstream operation. Large uploads need streaming or a multipart parser instead of an unbounded in-memory buffer.

Use Express middleware

Express has exposed express.json() and express.urlencoded() since 4.16.0. The separate body-parser package still exists; it was not simply removed.

const express = require('express');
const app = express();
app.use(express.json({ limit: '16kb' }));
app.use(express.urlencoded({ extended: false, limit: '16kb' }));
app.post('/api/user', (req, res) => {
  const body = req.body;
  if (!body || typeof body.name !== 'string' || typeof body.email !== 'string') {
    return res.status(400).json({ error: 'name and email must be strings' });
  }
  res.json({ accepted: true });
});
app.use((error, req, res, next) => {
  if (res.headersSent) return next(error);
  const status = error.status === 413 ? 413 : error.status === 400 ? 400 : 500;
  res.status(status).json({ error: status === 500 ? 'Server error' : 'Invalid request body' });
});
app.listen(3000, '127.0.0.1');

Run either server, not both on the same port. Middleware order matters, and parsers only handle matching content types. Send a JSON request with:

curl http://127.0.0.1:3000/api/user \
  -H 'Content-Type: application/json' \
  -d '{"name":"Ada","email":"ada@example.com"}'

See Node HTTP and Express's parser API.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting