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.
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.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan