The API client that handles auth so you don't have to.
Token attachment. Silent refresh. Race-condition safety. Works everywhere.
You've written the interceptor. You've debugged the race condition where five requests all tried to refresh at once. You've added the retry logic, the logger, the storage adapter. You've done it on every project.
API Kit does all of it — once, correctly.
ApiClient.getInstance({
baseURL: "https://api.example.com",
storage: myStorage,
hooks: {
onAuthFailure: () => router.push("/login"),
},
});
// That's it. Every request from here automatically:
// - attaches Bearer token
// - refreshes silently when it expires
// - retries if the server hiccups
// - routes exactly ONE refresh call even if 10 requests fire at once
const { data } = await client.get<User>("/api/me");Token lifecycle — fully automatic
- Attaches
Authorization: Bearer <token>to every request - Detects 401s and transparently refreshes + replays the failed request
- Pre-emptive refresh — renews the token before it expires, configurable via
preemptiveRefreshSeconds
Race-condition safe
- Concurrent 401s collapse into exactly one refresh call
- All queued requests drain together the moment refresh completes
- Pre-emptive refresh shares the same single-flight promise — no double-refresh under load
Platform agnostic
- Works in browser, React Native, and Node.js
- Bring your own storage — any object implementing
getAccessToken / setTokens / clearTokens - Ships with
MemoryTokenStorage,LocalStorageTokenStorage, andAsyncStorageTokenStorage - First-class support for
@devraj-labs/rn-storage-kit(encrypted keychain on mobile)
Production grade
- Exponential backoff with full jitter on transient errors (408, 429, 5xx)
- Auth event hooks —
onTokenRefreshed·onAuthFailure·onLogout - Bring-your-own logger — any
{ debug, info, warn, error }object (console,@devraj-labs/logger, etc.) - Singleton pattern — initialise once, use everywhere, no prop drilling
TypeScript first
- Full generics on every method:
client.get<User>("/api/me") - Exported interfaces for every extension point
npm install @devraj-labs/api-kit axios// bootstrap.ts — run this once at app entry (index.ts / App.tsx)
import { ApiClient, LocalStorageTokenStorage } from "@devraj-labs/api-kit";
ApiClient.getInstance({
baseURL: "https://api.example.com",
storage: new LocalStorageTokenStorage(),
hooks: {
onAuthFailure() {
window.location.href = "/login";
},
},
});// anywhere in your app — no config needed
import { ApiClient } from "@devraj-labs/api-kit";
const client = ApiClient.getInstance();
const { data: user } = await client.get<User>("/api/me");
const { data: post } = await client.post<Post>("/api/posts", { title });
const { data: updated } = await client.patch<Post>("/api/posts/1", { title });After login, store the tokens and every subsequent request is handled:
import { ApiClient } from "@devraj-labs/api-kit";
async function login(username: string, password: string) {
const res = await fetch("/auth/login", {
method: "POST",
body: JSON.stringify({ username, password }),
});
const tokens = await res.json();
// hand tokens to api-kit — it takes it from here
const client = ApiClient.getInstance();
await client.storage.setTokens(tokens);
}| Adapter | Environment | Security |
|---|---|---|
MemoryTokenStorage |
Any | Cleared on page reload |
LocalStorageTokenStorage |
Browser | Vulnerable to XSS — dev/demo only |
AsyncStorageTokenStorage |
React Native | Unencrypted — dev/demo only |
@devraj-labs/rn-storage-kit |
React Native | Encrypted keychain — recommended for prod |
Implement your own with three methods:
import type { TokenStorage } from "@devraj-labs/api-kit";
const myStorage: TokenStorage = {
getAccessToken: () => secureGet("access_token"),
getRefreshToken: () => secureGet("refresh_token"),
setTokens: (t) => secureSet(t),
clearTokens: () => secureClear(),
};Full guide → docs/storage.md
Pass any object with debug / info / warn / error — no adapter needed:
// console
ApiClient.getInstance({ ..., logger: console });
// @devraj-labs/logger
import { logger } from "@devraj-labs/logger";
ApiClient.getInstance({ ..., logger: logger.child({ module: "api-kit" }) });
// silent (default)
ApiClient.getInstance({ ...config }); // omit logger entirelyFull guide → docs/logger.md
| Getting Started | Install, minimal setup, platform examples |
| Configuration | All config options, types, and defaults |
| Token Storage | Built-in adapters, custom storage, rn-storage-kit |
| Logger Integration | Using @devraj-labs/logger or any compatible logger |
| Race Condition Guard | How concurrent 401s are handled |
| Pre-emptive Refresh | Proactive token renewal before expiry |
| Examples | Node.js · Browser · React Native · custom storage |
| Demo | Running the interactive demo app locally |
Hosted demo — no setup needed: https://web-self-delta-77.vercel.app/
See token refresh, race-condition guard, and live expiry countdown in action.
Login with alice / password123.
Or run it locally:
npm run demoOpens a Vite + React app at http://localhost:5173 backed by a local Express server.
Full guide → docs/demo.md
MIT © Devraj Labs