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

> Learn to send JSON data in a Node.js fetch POST request. Serialize with JSON.stringify, set Content-Type, and include duplex half for Undici compatibility.

- Repository: [Node.js/node](https://github.com/nodejs/node)
- Tags: how-to-guide
- Published: 2026-02-16

---

**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`](https://github.com/nodejs/node/blob/main/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:

```javascript
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:

```javascript
// post-json-example.mjs
const payload = {
  username: 'jdoe',
  email: 'jdoe@example.com',
  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`](https://github.com/nodejs/node/blob/main/deps/undici/src/lib/web/fetch/response.js). For JSON APIs, use the `.json()` method to parse the response body:

```javascript
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`](https://github.com/nodejs/node/blob/main/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`](https://github.com/nodejs/node/blob/main/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.