Request & Response Helpers

Content type handling, response conversion, and request body processing

CallApi automatically handles content types and response parsing, with utilities for common data formats.

Automatic Content-Type Detection and Assignment

Request bodies are automatically assigned the correct Content-Type header:

  • Objectsapplication/json
  • Query stringsapplication/x-www-form-urlencoded
  • FormDatamultipart/form-data (browser handled)
content-types.ts
import { callApi } from "@zayne-labs/callapi";

// Automatically sets Content-Type: application/json
await callApi("/api/users", {
	method: "POST",
	body: { name: "John", age: 30 },
});

// Automatically sets Content-Type: application/x-www-form-urlencoded
await callApi("/api/form", {
	method: "POST",
	body: "name=John&age=30",
});

// Override when needed
await callApi("/api/custom", {
	method: "POST",
	body: data,
	headers: { "Content-Type": "application/custom+json" },
});

Smart Response Parsing

Responses are automatically parsed based on their Content-Type header:

  • JSON types (application/json, application/vnd.api+json) → Parsed as JSON
  • Text types (text/*, application/xml) → Parsed as text
  • Everything else → Parsed as blob
response-parsing.ts
import { callApi } from "@zayne-labs/callapi";

// Automatically parsed based on Content-Type response header
const { data: user } = await callApi("/api/user"); // JSON
const { data: html } = await callApi("/page.html"); // Text
const { data: image } = await callApi("/avatar.png"); // Blob

Manual Response Type Override

You can still manually specify the response type if needed:

Available response types include:

  • All response types from the Fetch API:
    • json() (default fallback)
    • text()
    • blob()
    • arrayBuffer()
    • formData()
  • stream - Returns the direct ReadableStream
api.ts
import { callApi } from "@zayne-labs/callapi";

const { data: imageBlob } = await callApi("/image", {
	responseType: "blob",
});

const { data: rawText } = await callApi("/data.json", {
	responseType: "text", // Get JSON as raw text
});

const { data: buffer } = await callApi("/binary", {
	responseType: "arrayBuffer",
});

const { data: stream } = await callApi("/large-file", {
	responseType: "stream", // ReadableStream for progressive processing
});

Custom Response Parser

Use a custom parser function:

custom-parser.ts
import { callApi } from "@zayne-labs/callapi";

const { data } = await callApi("/api/data", {
	responseParser: (responseString) => customParser(responseString),
});

Result Modes

The resultMode option dictates how CallApi processes and returns the final result:

  • "all" (default): Returns { data, error, response }. Standard lifecycle.
  • "onlyData": Returns only the data from the response.
  • "onlyResponse": Returns only the Response object.
  • "fetchApi": Also returns only the Response object, but also skips parsing of the response body internally and data/errorData schema validation.
  • "withoutResponse": Returns { data, error }. Standard lifecycle, but omits the response property.

The fetchApi Mode

The fetchApi mode is designed for scenarios where you want most of the library's benefits (URL resolution, plugins, hooks) don't want any internal parsing of the response body to occur, just like the Fetch Api.

When set to fetchApi:

  1. No Parsing: The library will not attempt to read or parse the response body.
  2. No Validation: Both data error-data validation are skipped.

By default, simplified modes ("onlyData", "onlyResponse", "fetchApi") do not throw errors. Success/failure should be handled via hooks or by checking the return value (e.g., if (data) or if (response?.ok)).

To force an exception, set throwOnError: true.

Request Body Utilities

Object Bodies

Objects are automatically JSON stringified:

object-bodies.ts
import { callApi } from "@zayne-labs/callapi";

// CallApi handles this automatically
await callApi("/api/user", {
	method: "POST",
	body: { name: "John", age: 30 },
});

// Equivalent to manual fetch:
// fetch("/api/user", {
//   method: "POST",
//   headers: { "Content-Type": "application/json" },
//   body: JSON.stringify({ name: "John", age: 30 }),
// });

Custom Body Serializer

Override the default JSON serialization:

custom-serializer.ts
import { callApi } from "@zayne-labs/callapi";

await callApi("/api/data", {
	method: "POST",
	body: { name: "John", age: 30 },
	bodySerializer: (body) => customSerialize(body),
});

Query String Bodies

Convert objects to URL-encoded strings:

query-string-bodies.ts
import { callApi } from "@zayne-labs/callapi";
import { toQueryString } from "@zayne-labs/callapi/utils";

await callApi("/api/search", {
	method: "POST",
	body: toQueryString({ name: "John", age: 30 }),
});
// Body: "name=John&age=30"
// Content-Type: application/x-www-form-urlencoded

FormData Bodies

Convert objects to FormData with intelligent type handling:

formdata-bodies.ts
import { callApi } from "@zayne-labs/callapi";
import { toFormData } from "@zayne-labs/callapi/utils";

await callApi("/api/upload", {
	method: "POST",
	body: toFormData({
		avatar: imageFile, // Files/blobs added directly
		tags: ["dev", "designer"], // Arrays become multiple entries
		metadata: { role: "admin" }, // Objects are JSON stringified
		name: "John", // Primitives added as-is
	}),
});

How toFormData handles different types:

  • Files/Blobs → Added directly
  • Arrays → Multiple entries with same key
  • Objects → JSON stringified
  • Primitives → Added as-is
Edit on GitHub

Last updated on

On this page