Home / Blog / JavaScript & TypeScript

API & webhooks

Node.js REST API POST: Send JSON and Handle Empty Success

Send a JSON POST with Node fetch, preserve content-type and body agreement, handle 201 and 204 correctly, and avoid unsafe automatic retries.

Confirm what the write operation accepts and returns

Before implementing a REST API POST, read the endpoint's request and response contract. Determine whether it accepts JSON, which fields are required, what authentication it uses and whether success returns a representation. A client that always calls response.json can fail on a legitimate empty success response after the server has already completed the write.

Use a current Node.js runtime that supports built-in fetch. The Node fetch documentation shows request options for a JSON body. Serialization and Content-Type must agree: setting a JSON header does not convert an arbitrary JavaScript object into transmitted JSON text.

Serialize the request and inspect the response once

async function postNote(url, note, fetcher = fetch) {
  const body = JSON.stringify(note);
  if (body === undefined) throw new Error("A JSON value is required.");
  const response = await fetcher(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json"
    },
    body,
    signal: AbortSignal.timeout(10000)
  });
  const text = await response.text();
  if (!response.ok) {
    throw new Error(`Create note failed: HTTP ${response.status}`);
  }
  return {
    status: response.status,
    location: response.headers.get("location"),
    data: text ? JSON.parse(text) : null
  };
}

This helper assumes a JSON response when a successful body is present. It returns null for an empty body and preserves a readable Location header. The caller can distinguish a 201 response describing a new resource from a 204 response with no content. The helper does not invent a resource identifier when the server returns none.

The request serializer runs before fetch. Circular objects and unsupported BigInt values therefore fail before any network write. An undefined top-level value is rejected explicitly. Validate the application's note fields separately; JSON can represent many values that are not valid notes.

Keep request data and headers consistent

For a note containing quotation marks or line breaks, JSON.stringify supplies the JSON escaping. Avoid building the body with concatenated quotation marks, and do not apply URL encoding to the whole JSON document. A form-encoded or multipart endpoint needs a different body construction path and a matching content type.

Let fetch manage transport framing for this request. A hand-written Content-Length based on JavaScript string length can be wrong for Unicode. If a lower-level client requires you to supply a length, measure the actual encoded bytes rather than assuming that each visible character occupies one byte.

Authentication is intentionally absent from the sample. Add the provider's documented mechanism and obtain secrets from the application's appropriate configuration or credential flow. Never place real tokens in an article example, a committed fixture or a screenshot of a failing request.

Preserve the difference between failure and uncertainty

A readable 409 might indicate a conflict requiring the caller to review state, while a 415 points to a request representation the endpoint does not accept. The helper preserves the status in its error message. It does not treat every error as a reason to repeat the write.

A timeout or lost connection can leave the outcome uncertain. The server might have committed the note before the response was interrupted. Use the API's documented idempotency or reconciliation mechanism when one exists; inventing an unrecognized idempotency header does not make a write safely repeatable.

Exercise success bodies, empty responses and rejection

Test a JSON 201 response, an empty 204 response, a conflict and malformed JSON in a successful response. Capture the outgoing method, headers and body. Parse the captured body independently and compare its fields with the original note, including Unicode and embedded quotation marks.

Include a serialization failure and verify that the controlled fetcher was never called. Also assert that a non-success response causes exactly one request. These tests prevent a helper from quietly introducing retries or sending the text representation of an object instead of JSON.

Use the REST API Testing Tool only against an endpoint where you intend and are authorized to perform the write. For repeatable development checks, use a local fixture that records the request without creating production data. Test the real service separately under its documented test-environment rules.

Keep the returned status available to the caller instead of returning only data. That allows the surrounding workflow to show whether a representation was created, an empty success was received or the operation needs a separate follow-up request.