How to Send JSON Data in a Node.js Fetch POST Request

To send JSON data in a Node.js fetch POST request, serialize your payload with JSON.stringify(), set the Content-Type header to application/json, and include duplex: 'half' in the request options to satisfy the Undici-based implementation.

Node.js ships with a native Fetch API (exposed as globalThis.fetch) built on top of the Undici library, eliminating the need for external HTTP clients. When constructing a nodejs fetch post request that transmits JSON payloads, you must account for specific internal validation rules in the Undici source code that differ from browser implementations.

Why Node.js Fetch Requires the duplex Option

The Node.js fetch implementation lives in the deps/undici/src/lib/web/fetch/ directory of the Node.js source repository. Unlike browser fetch, the Undici-based Request class enforces strict body handling rules for streaming compatibility.

In deps/undici/src/lib/web/fetch/request.js, the constructor validates that any request containing a body (such as POST, PUT, or PATCH) must specify the duplex option. Lines 512-518 contain the validation logic:

if (init.body != null) {
  // ...
  if (!init.duplex) {
    throw new TypeError('RequestInit: duplex option is required when sending a body.');
  }
}

Setting duplex: 'half' signals that the request body will be streamed only once, which is the standard behavior for JSON payloads and satisfies the WHATWG Fetch spec requirements as implemented in Node.js.

Complete Example: Sending JSON in a Node.js Fetch POST Request

To correctly transmit JSON data, follow these implementation steps:

  1. Serialize the payload using JSON.stringify()
  2. Set the Content-Type header to application/json
  3. Include duplex: 'half' in the request initialization
  4. Specify the HTTP method as POST

Here is a production-ready implementation:

// post-json-example.mjs
const payload = {
  username: 'jdoe',
  email: '[email protected]',
  roles: ['admin', 'user']
};

async function postJson(url, data) {
  const response = await fetch(url, {
    method: 'POST',
    // Required for Node.js fetch when sending a body
    duplex: 'half',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: JSON.stringify(data)
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return response.json();
}

// Usage
postJson('https://api.example.com/users', payload)
  .then(data => console.log('Success:', data))
  .catch(error => console.error('Error:', error));

Handling the Response

After sending the request, the fetch promise resolves to a Response object implemented in deps/undici/src/lib/web/fetch/response.js. For JSON APIs, use the .json() method to parse the response body:

const data = await response.json();

This method returns a promise that resolves to the parsed JavaScript object, handling the underlying stream consumption automatically.

Summary

  • Node.js provides a native Fetch API built on Undici, accessible via globalThis.fetch without installing external packages
  • When sending JSON in a nodejs fetch post request, you must include duplex: 'half' to satisfy the body validation in deps/undici/src/lib/web/fetch/request.js
  • Always serialize your payload with JSON.stringify() and set the Content-Type: application/json header
  • The implementation files reside in deps/undici/src/lib/web/fetch/ within the Node.js source repository

Frequently Asked Questions

Why do I get "RequestInit: duplex option is required when sending a body" in Node.js?

This error originates from the Request class constructor in deps/undici/src/lib/web/fetch/request.js. Node.js fetch requires the duplex option for any request containing a body to handle streaming correctly according to the WHATWG Fetch specification. Set duplex: 'half' in your request options to resolve this error.

Do I need to install node-fetch or undici to use fetch in modern Node.js?

No. Since Node.js v18, the Fetch API is available globally as globalThis.fetch without installing external packages. The implementation is built on Undici, which is bundled directly into the Node.js runtime in deps/undici/.

What is the difference between duplex: 'half' and duplex: 'full'?

The duplex option controls how the request body stream is handled. 'half' indicates that the client sends the body once and does not need to read from it again, which is standard for JSON POST requests. 'full' duplex would allow bidirectional streaming, but the Node.js fetch implementation primarily expects 'half' for typical HTTP requests with bodies.

Should I use JSON.stringify() or can I pass the object directly to the body?

You must use JSON.stringify(). The fetch body option accepts various types including strings, but passing a raw JavaScript object will result in the object being converted to [object Object] rather than valid JSON. Always serialize your payload with JSON.stringify() and set the appropriate Content-Type header.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →