# CallApi: Advanced Options
URL: /docs/advanced-options
Source: https://raw.githubusercontent.com/zayne-labs/callapi/refs/heads/main/apps/docs/content/docs/advanced-options.mdx
Advanced configuration options for fine-tuned control
This page covers advanced CallApi configuration options for specialized use cases and fine-tuned control over request/response handling.
Response Cloning [#response-cloning]
Enable `cloneResponse` to read the response multiple times in different places (e.g., in hooks and main code).
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const callApi = createFetchClient({
cloneResponse: true,
onSuccess: async ({ response }) => {
const data = await response.json();
console.log(data);
},
});
```
Automatically enabled when using
`dedupeStrategy: "defer"`
.
Custom Fetch Implementation [#custom-fetch-implementation]
Replace the default fetch function for testing or using alternative HTTP clients.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
// Mock fetch for testing
const mockFetch = async (url: string | Request | URL, init?: RequestInit) => {
return Response.json(
{ mocked: true },
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
};
const callApi = createFetchClient({
customFetchImpl: mockFetch,
});
```
Skip Auto-Merge [#skip-auto-merge]
Control which configuration parts skip automatic merging between base and instance configs.
**How it works:**
By default, CallApi automatically merges base config with instance config. When you set `skipAutoMergeFor`, CallApi stops merging that part - **you become responsible for manually spreading the skipped object to preserve instance values**.
This is useful when you need to manually spread instance options and then selectively override specific nested properties with defaults.
**Available options:**
* `"options"` - Skips auto-merge of extra options (plugins, hooks, meta, etc.). You must manually spread `ctx.options`.
* `"request"` - Skips auto-merge of request options (headers, body, method, etc.). You must manually spread `ctx.request`.
* `"all"` - Skips auto-merge of both. You must manually spread both `ctx.options` and `ctx.request`.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const client = createFetchClient((ctx) => ({
baseURL: "https://api.example.com",
plugins: [authPlugin()],
skipAutoMergeFor: "options",
// Spread instance options first
...(ctx.options as object),
// Then provide defaults for nested properties
meta: {
...ctx.options.meta,
auth: {
signInRoute: "/auth/signin",
// Instance values override these defaults
...ctx.options.meta?.auth,
},
},
}));
// Instance can override nested auth properties
await client("/protected", {
meta: {
auth: {
redirectOnError: false,
},
},
});
```
**Why use `skipAutoMergeFor: "options"`?**
Without it, CallApi automatically merges `ctx.options` with your base config, which means you can't provide defaults for nested properties that can be overridden.
With `skipAutoMergeFor: "options"`:
1. CallApi stops automatically merging `ctx.options`
2. **You must manually spread `ctx.options`** to preserve instance values: `...(ctx.options as object)`
3. Then you can provide defaults for nested properties
4. Instance-provided nested values override your defaults because they're spread last
If you don't manually spread the skipped object, instance values will be lost!
Body Serialization [#body-serialization]
Customize how request body objects are serialized.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const callApi = createFetchClient({
bodySerializer: (data) => {
// Convert object to URL-encoded form data
const formData = new URLSearchParams();
Object.entries(data).forEach(([key, value]) => {
formData.append(key, String(value));
});
return formData.toString();
},
});
```
Response Parsing [#response-parsing]
Customize how response strings are parsed.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
// Parse XML responses
const xmlClient = createFetchClient({
responseParser: (responseString) => {
const parser = new DOMParser();
const doc = parser.parseFromString(responseString, "text/xml");
return xmlToObject(doc);
},
});
// Custom JSON parser with error handling
const customClient = createFetchClient({
responseParser: (responseString) => {
try {
return JSON.parse(responseString);
} catch {
return { error: "Invalid JSON", raw: responseString };
}
},
});
```
Default HTTP Error Messages [#default-http-error-messages]
Customize the default error message when the server doesn't provide one.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const client = createFetchClient({
defaultHTTPErrorMessage: ({ response }) => {
switch (response.status) {
case 401: {
return "Authentication required";
}
case 403: {
return "Access denied";
}
case 404: {
return "Resource not found";
}
default: {
return `Request failed with status ${response.status}`;
}
}
},
});
```
Meta Field [#meta-field]
Associate metadata with requests for logging, tracing, or custom handling in hooks and middleware.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const callApi = createFetchClient({
onError: ({ options, error }) => {
// Access metadata passed with the request
logError({
userId: options.meta?.userId,
requestId: options.meta?.requestId,
error,
});
},
});
await callApi("/api/data", {
meta: {
userId: currentUser.id,
requestId: generateId(),
},
});
```
Types [#types]
For complete type information on all options, see:
* [Extra Options](/docs/extra-options) - Instance-level options
* [Base Extra Options](/docs/base-extra-options) - Base client options
* [Request Options](/docs/request-options) - Fetch API options
# CallApi: Authorization
URL: /docs/authorization
Source: https://raw.githubusercontent.com/zayne-labs/callapi/refs/heads/main/apps/docs/content/docs/authorization.mdx
Learn how to use convenience authorization helpers callApi provides
CallApi provides convenient authorization helpers via the `auth` property, making it easy to add authentication headers to your requests without manually constructing Authorization headers.
**Security Best Practice:** Never hardcode sensitive tokens or credentials in your source code. Always
use environment variables, secure storage, or runtime token retrieval functions.
Bearer [#bearer]
Since Bearer is the most common authorization type, passing a string to `auth` automatically generates a `Bearer` Authorization header. You can also use an object with a `bearer` property.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
// Passing a string
const callBackendApi = createFetchClient({
baseURL: "http://localhost:3000",
auth: "my-token", // [!code highlight]
});
// Passing an object
const result = await callBackendApi("/users/123", {
auth: {
type: "Bearer",
value: "my-token",
},
});
```
The above is equivalent to writing the following with Fetch:
```ts
fetch("http://localhost:3000/users/123", {
headers: {
Authorization: `Bearer my-token`,
},
});
```
You can also pass a function that returns a string or a promise that resolves to a string. This is useful for retrieving tokens dynamically:
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const callBackendApi = createFetchClient({
baseURL: "http://localhost:3000",
});
const result = callBackendApi("/users/123", {
auth: {
type: "Bearer",
value: () => authStore.getToken(),
},
});
```
The function will be called only once when the request is made. If it returns undefined or null, the
header will not be added to the request. This allows for conditional authentication.
Token [#token]
Similar to Bearer authorization, but uses `Token` as the header prefix instead of `Bearer`.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const callBackendApi = createFetchClient({
baseURL: "http://localhost:3000",
auth: {
type: "Token",
value: "my-token",
},
});
const result = await callBackendApi("/users/123");
```
The above is equivalent to writing the following with Fetch:
```ts
fetch("http://localhost:3000/users/123", {
headers: {
Authorization: `Token my-token`,
},
});
```
Basic [#basic]
Basic authentication adds username and password to the `Authorization` header, automatically base64 encoded.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const callBackendApi = createFetchClient({
baseURL: "http://localhost:3000",
auth: {
type: "Basic",
username: "my-username",
password: "my-password",
},
});
```
You can also pass async getter functions for the username and password fields.
```ts
const { data } = await callApi("/api/data", {
auth: {
type: "Basic",
username: async () => await getUsername(),
password: async () => await getPassword(),
},
});
```
Custom [#custom]
For custom authorization schemes not supported by default, use the `Custom` type with `prefix` and `value` properties.
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const callBackendApi = createFetchClient({
baseURL: "http://localhost:3000",
auth: {
type: "Custom",
prefix: "SomePrefix",
value: "my-token",
},
});
const result = await callBackendApi("/users/123");
```
The above is equivalent to writing the following with Fetch:
```ts
fetch("http://localhost:3000/users/123", {
headers: { Authorization: `SomePrefix my-token` },
});
```
Advanced Examples [#advanced-examples]
Token Refresh [#token-refresh]
Combine `auth` with the `onError` hook and `retry` behavior to implement automatic token refreshing:
```ts title="api.ts"
import { createFetchClient } from "@zayne-labs/callapi";
const api = createFetchClient({
baseURL: "https://api.example.com",
// The auth function is called on every request, ensuring we use a fresh token
auth: () => getAccessToken(),
// We can automatically retry failed requests that were unauthorized
onResponseError: async ({ response, options }) => {
if (response.status === 401) {
// Force a network refresh to get a new token
await refreshAccessToken();
// Retry the original request with the new token
options.retryAttempts = 1;
}
},
});
```
Conditional Auth [#conditional-auth]
Conditionally trigger authentication by returning `null` or `undefined` from your auth function when the user is not authenticated:
```ts title="api.ts"
const client = createFetchClient({
baseURL: "https://api.example.com",
auth: async () => {
// Only add auth if user is logged in
const isLoggedIn = await checkAuthStatus();
// Returning null means no auth header will be added
return isLoggedIn ? await getToken() : null;
},
});
// Or dynamically per-request
async function makeRequest(endpoint: string, requiresAuth: boolean) {
return callApi(endpoint, {
auth: requiresAuth ? () => getToken() : undefined,
});
}
```
Global Auth with Override [#global-auth-with-override]
Define a global authentication strategy when creating a client and override or disable it for specific requests:
```ts title="api.ts"
const client = createFetchClient({
baseURL: "https://api.example.com",
auth: () => getSessionToken(),
});
// Uses the global auth token
const userData = await client("/api/user");
// Override with a different token for a specific request
const adminData = await client("/api/admin", {
auth: () => getAdminToken(),
});
// Disable auth entirely for a specific request
const publicData = await client("/api/public", {
auth: undefined,
});
```
# CallApi: Base Extra Options
URL: /docs/base-extra-options
Source: https://raw.githubusercontent.com/zayne-labs/callapi/refs/heads/main/apps/docs/content/docs/base-extra-options.mdx
Options that can be passed to the createFetchClient function
This page documents the configuration options specific to `createFetchClient`. These options define the base behavior for all requests made with the created client instance.