bhylu.com

Advertisements

Node.js Request Bodies

A request body arrives in chunks. Limit retained bytes before parsing, reject malformed JSON and distinguish an oversized body from a syntactically invalid one.

How it works

This controlled server keeps at most 32 body bytes and discards excess chunks rather than appending them. It waits for the small local request to end before replying. Production ingestion also needs time, connection and rate limits; bounded retained memory is only one part of that contract. JSON parsing checks syntax, while the following application validation checks whether the requested quantity makes sense.

Run the code

Run this example locally with Node.js. The exact sources are reviewed on Node 22.15.1 and Node 24.22.1 unless a specialised lab states otherwise. Save the complete source as request-body.mjs in a separate empty exercises folder and run node request-body.mjs. There is no Node.js browser runner on Bhylu; the plain JavaScript playground does not provide these filesystem, server or process APIs.

Download the complete source. Ordinary Node scripts need no Apache, PHP or MySQL service. File examples own their temporary folders; network fixtures use loopback and available temporary ports.

Example

request-body.mjs

import http from 'node:http';
import { once } from 'node:events';
function readBody(request, limit) {
  return new Promise((resolve, reject) => {
    let bytes = 0,
      large = false;
    const chunks = [];
    request.on('data', (chunk) => {
      bytes += chunk.length;
      if (bytes > limit) large = true;
      else chunks.push(chunk);
    });
    request.on('error', reject);
    request.on('end', () =>
      large
        ? reject(Object.assign(new Error('Too large'), { status: 413 }))
        : resolve(Buffer.concat(chunks).toString('utf8')),
    );
  });
}
const server = http.createServer((request, response) => {
  (async () => {
    try {
      const data = JSON.parse(await readBody(request, 32));
      response.writeHead(200).end(String(data.quantity));
    } catch (error) {
      response.writeHead(error.status ?? 400).end('Rejected');
    }
  })().catch(() => response.destroy());
});
server.requestTimeout = 2000;
server.listen(0, '127.0.0.1');
await once(server, 'listening');
const base = 'http://127.0.0.1:' + server.address().port;
try {
  for (const body of ['{"quantity":2}', '{broken', 'x'.repeat(40)]) {
    const response = await fetch(base, { method: 'POST', body });
    console.log(response.status, await response.text());
  }
} finally {
  server.closeAllConnections();
  await new Promise((resolve, reject) =>
    server.close((error) => (error ? reject(error) : resolve())),
  );
}

Expected result

200 2
400 Rejected
413 Rejected

The accepted JSON, malformed source and excessive input take different paths. The example does not accept uploads or arbitrary content types and does not claim that a valid JSON quantity has already passed a domain range check.

Use the Node.js API reference for the precise version contract.