-
Notifications
You must be signed in to change notification settings - Fork 0
Home
The HTTP layer under TanStack Query, SWR and Vue Query — it handles authentication so they don't have to.
A TypeScript-first API client focused on secure browser auth: coalesced token refresh, Web Worker token isolation, and cross-tab session sync. Zero runtime dependencies.
npm install @mrzr/api-clientimport { createClient } from "@mrzr/api-client";
export const api = createClient({ baseUrl: "https://api.example.com" });
await api.login({ email, password });
const { data } = await api.get<User[]>("/users");Most HTTP clients treat auth as something you bolt on with interceptors. This one treats it as the product.
Use it if any of these are real problems:
- An expired token makes 20 concurrent requests each fire their own refresh
- You want tokens off the main thread, where XSS can't read them
- Logging out in one tab should log out the others
- You use httpOnly cookies and can't tell on page load whether a session exists
- You need refresh to work during a five-minute upload
Use something else if not. For a small fetch wrapper, ky is excellent — a third of the size, built-in retry. For the biggest ecosystem and legacy support, axios. Neither is trying to solve browser auth, and this isn't trying to out-ky ky.
Including the rows where this library loses.
| axios | ky | @mrzr/api-client | |
|---|---|---|---|
| Zero runtime dependencies | ✗ | ✓ | ✓ |
| Bundle, min+gzip | ~14 KB | ~4 KB | 13.4 KB |
| Built on | XHR / node:http | fetch | fetch |
| Retry with backoff | via axios-retry
|
✓ built in | ✗ (not yet) |
| Interceptors / hooks | ✓ global | ✓ global | per-request transforms |
| Coalesced token refresh | build it yourself | build it yourself | ✓ built in |
| Web Worker token isolation | ✗ | ✗ | ✓ |
| Cross-tab auth sync | ✗ | ✗ | ✓ |
| httpOnly cookie session restore | ✗ | ✗ | ✓ |
| Cancel by URL pattern / scope | ✗ | ✗ | ✓ |
| CSRF double-submit | partial | ✗ | ✓ |
-
Size. 13.4 KB is axios-territory and 3× ky. ~3.8 KB of it is the inlined worker, which ships even with
worker: false— a runtime flag can't be tree-shaken. -
Retry. Not implemented. It has to interact correctly with refresh-and-retry, cancellation and
takeLatest; shipping it half-right would be worse than not shipping it.
| Coalesced refresh | 50 simultaneous 401s trigger exactly one refresh call — a shared promise, not a polling loop. |
| Worker isolation | Requests run in a Web Worker by default, so tokens never enter the main-thread heap. |
| Cross-tab sync | Login, logout and refresh propagate over BroadcastChannel, with leader election. |
| httpOnly cookie mode | Including restoreSession(), which answers the "am I logged in?" question cookies make unanswerable from JS. |
| Opt-in cancellation | Cancel by URL pattern, scope or key on page change or modal close. Real aborts, worker mode included. |
| Real upload support |
FormData, File, Blob, ArrayBuffer, typed arrays and ReadableStream, with refresh handled mid-upload. |
| One request engine | The worker and main thread run the same compiled code, so behaviour can never drift. |
| Runs anywhere | React, Vue, Svelte, Angular, Next.js, Nuxt, SvelteKit, plain <script>, Node 20+, Deno, Bun, Cloudflare Workers. |
It sits under TanStack Query, SWR or Vue Query — it doesn't replace them.
useQuery({
queryKey: ["users"],
queryFn: ({ signal }) => api.get<User[]>("/users", { signal }).then((r) => r.data),
});Failures reject with a typed ApiError, which is what Query and SWR need to mark a request failed. Cancellation resolves instead, flagged canceled: true, so a route change never looks like an error.
- Installation — install, requirements, framework notes
- Quick Start — your first client and request
- Core Concepts — the envelope, the engine, the execution modes
-
Requests —
get/post/put/patch/delete, URL building, params - Request Config — every per-request option, explained
- Cancellation — cancel on page change, modal close or a new keystroke
-
Responses and Errors —
IRes,ApiError, throwing vs. envelope - Uploads and Binary Bodies — FormData, Blob, streams, long uploads
- Authentication — header mode, cookie mode, login/logout, seeding tokens
- Token Refresh — reactive 401 retry, proactive refresh, custom token shapes
- Storage Adapters — memory/local/session/cookie and custom adapters
- CSRF Protection — double-submit, worker-mode caveats
- Web Worker Isolation — how it works, what it protects, when it disables itself
- Multi-Tab Sync — BroadcastChannel, leader election
-
Logging and Observability —
log,onLog,onError - Security Model — the threat model, honestly stated
-
Client Options — the full
ClientOptionstable - API Reference — every export, signature by signature
- TypeScript Types — the complete public type surface
- Framework Recipes — React, TanStack Query, SWR, Next.js, Nuxt, Svelte, Angular, Node
- Cookbook — practical patterns and snippets
- Migration Guide — coming from axios, or from an older envelope-style setup
- Troubleshooting — every error message and what to do about it
- FAQ — short answers to common questions
- Contributing — repo layout, build pipeline, verification suite
import { createClient, ApiError } from "@mrzr/api-client";
const api = createClient({
baseUrl: "https://api.example.com",
storage: "memory", // safest default
worker: true, // tokens never reach the main thread
multiTab: true, // tabs stay in sync
});
// Log in — tokens are captured and stored for you
await api.login({ email: "a@b.com", password: "secret" });
// Typed request with URL templating and nested query params
const { data } = await api.get<User[]>("/orgs/{org}/users", {
addTemplateToUrl: { org: "acme" },
params: { page: 1, filter: { active: true } },
});
// Failures reject with a typed error
try {
await api.post("/orders", { sku: "X" });
} catch (e) {
if (e instanceof ApiError) console.error(e.statusCode, e.errors);
}
await api.logout(); // clears tokens in every tabMIT © mohammadreza-zr
Getting started
Requests
Authentication
Advanced
Reference
Guides