Skip to content
rhttp.io

Client Configuration

createClientHttp() is the factory for browser and Client Component code. It wraps createHttp() with secure defaults so the most common setup is one line, then lets you override anything.

import { createClientHttp } from "rhttp.io/client"; 
 
const http = createClientHttp({
  baseURL: "https://api.example.com",
});
 
// credentials, JSON headers, CSRF prefetch, and token auto-injection
// from localStorage are already configured.
const { data } = await http.get<User>("/me");

Signature

function createClientHttp(config?: CreateHttpConfig): HttpClientInstance;

It accepts the exact same CreateHttpConfig shape as the core factory — every option documented there applies here too.

What it configures for you

createClientHttp() builds on createHttp() and applies three layered defaults. Your explicit config always wins via a shallow merge.

1. Smart defaultFetchOptions merge

Rather than replacing your fetch options, the factory merges them over browser-secure defaults. This means you can pass defaultFetchOptions without losing credentials or your Content-Type.

// Internally, createClientHttp builds:
const clientDefaults = {
  credentials: "include" as const,
  headers: { "Content-Type": "application/json" },
};
 
const mergedFetchOptions = {
  ...clientDefaults,
  ...config.defaultFetchOptions,
  headers: {
    ...clientDefaults.headers,
    ...(config.defaultFetchOptions?.headers || {}),
  }, 
};

The effective precedence is therefore:

config.defaultFetchOptions.headers   (highest)
config.defaultFetchOptions.*         (credentials, mode, …)
clientDefaults.headers               ("Content-Type": "application/json")
clientDefaults.credentials           ("include")

2. CSRF, on and prefetched

CSRF protection is enabled by default for the browser, with a sensible endpoint and a warm-up fetch at construction time.

// Internally:
createHttp({
  // …your config…
  csrf: {
    enabled: true,
    cookieName: "csrf-token",
    headerName: "X-CSRF-Token",
    fetchEndpoint: "/api/csrf",
    prefetch: true, 
    ...config.csrf, // your overrides win
  },
});

The token is fetched once from GET /api/csrf (cached and shared), read from the csrf-token cookie when present, and injected as X-CSRF-Token on POST, PUT, PATCH, and DELETE. See CSRF Protection for per-request control and endpoint expectations.

3. Token auto-injection

A default getToken reads the access token from localStorage on every request, so you don't have to wire an interceptor just to attach a bearer.

const defaultGetToken = () => {
  if (typeof window !== "undefined") {
    return localStorage.getItem("access_token"); 
  }
  return null;
};
 
createHttp({
  // …your config…
  auth: {
    scheme: "Bearer",
    getToken: config.auth?.getToken || defaultGetToken, 
    ...config.auth,
  },
});

The resolved token is sent as Authorization: Bearer <token> (lowercased to authorization on the wire). Set or update it with localStorage.setItem("access_token", newToken).

Putting it together: a production client

import { createClientHttp } from "rhttp.io/client";
import { createRefreshAuthInterceptor } from "rhttp.io";
 
export const http = createClientHttp({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 15_000,
 
  // Resilience
  retry: { attempts: 3, strategy: "exponential", delay: 300, maxDelay: 5_000 },
  circuitBreaker: { enabled: true, failureThreshold: 5, successThreshold: 2, timeout: 30_000 },
  requestPool: { enabled: true, maxConcurrent: 5 },
 
  // Read-heavy endpoints benefit from SWR; mutations are not cached.
  cache: { enabled: true, ttl: 60_000, strategy: "stale-while-revalidate" },
 
  // Observability in development only.
  observability: { logger: import.meta.env.DEV, metrics: import.meta.env.DEV, tracing: true },
 
  // Merge custom fetch options without losing the secure defaults.
  defaultFetchOptions: { mode: "cors" },
});
 
// Refresh expired access tokens transparently.
http.interceptors.response.use(
  (response) => response,
  createRefreshAuthInterceptor(http, {
    refreshToken: async () => {
      const res = await fetch("/api/auth/refresh", { method: "POST", credentials: "include" });
      if (!res.ok) return null;
      const { token } = (await res.json()) as { token: string };
      return token;
    },
    onTokenRefreshed: (token) => localStorage.setItem("access_token", token),
  }),
);

Overriding the defaults

Every default is overridable. Common tweaks:

const http = createClientHttp({
  baseURL: "https://api.example.com",
 
  // Use a different localStorage key.
  auth: { getToken: () => localStorage.getItem("jwt") },
 
  // Point CSRF at a different endpoint, or disable it entirely.
  csrf: { enabled: false },
  // csrf: { fetchEndpoint: "/csrf-token", headerName: "X-XSRF-TOKEN" },
 
  // Turn off the JSON Content-Type for FormData uploads.
  defaultFetchOptions: { headers: {} },
});