@zayne-labs/callapi
v1.16.3
Published
A lightweight wrapper over fetch with quality of life improvements like built-in request cancellation, retries, interceptors and more
Downloads
1,562
Maintainers
Readme
Why CallApi?
Fetch is too basic for real apps. You end up writing the same boilerplate: error handling, retries, deduplication, response parsing etc. CallApi handles all of that and practically more.
Fetch-style API. ~7KB. All kinds of convenience features. Zero dependencies.
import { callApi } from "@zayne-labs/callapi";
const { data, error } = await callApi("/api/users");Features
Request Deduplication
User spam-clicks a button? Handled. No race conditions.
const req1 = callApi("/api/user");
const req2 = callApi("/api/user"); // Cancels req1 (can be configured to share its response instead)Smart Response Parsing
Looks at Content-Type and parses accordingly.
const { data } = await callApi("/api/data"); // JSON? Parsed.Error Handling
Errors are returned, not thrown, so every failure is handled in one place.
import { isHTTPError, isValidationError } from "@zayne-labs/callapi/utils";
const { data, error } = await callApi("/api/users");
if (isHTTPError(error)) {
console.log(error.errorData); // The API's error response body
}
if (isValidationError(error)) {
console.log(error.errorData); // The schema validation issues
}Prefer try/catch? Set throwOnError: true.
Retries
Supports exponential backoff and custom retry conditions.
await callApi("/api/data", {
retryAttempts: 3,
retryStrategy: "exponential",
retryStatusCodes: [429, 500, 502, 503],
});Schema Validation
TypeScript-first types with runtime validation.
import { createFetchClient } from "@zayne-labs/callapi";
import { defineSchema } from "@zayne-labs/callapi/utils";
import { z } from "zod";
const callMainApi = createFetchClient({
schema: defineSchema({
"/users/:id": {
data: z.object({
id: z.number(),
name: z.string(),
}),
},
}),
});
// Fully typed + validated
const { data: user, error } = await callMainApi("/users/:id", {
params: { id: 123 },
});
if (!error) {
user.name; // string
}Hooks
Hook into CallApi's lifecycle at any point.
const api = createFetchClient({
onRequest: ({ request }) => {
request.headers.Authorization = `Bearer ${token}`;
},
onError: ({ error }) => {
Sentry.captureException(error);
},
onResponseStream: ({ event }) => {
console.log(`Downloaded ${event.progress}%`);
},
});Plugins
Extend functionality with setup, hooks, and middleware.
import { createFetchClient, type GetCallApiContext } from "@zayne-labs/callapi";
import { definePluginWithContext } from "@zayne-labs/callapi/utils";
// Types the metadata this plugin reads and writes
type MetricsContext = GetCallApiContext<{ Meta: { startTime?: number } }>;
const metricsPlugin = definePluginWithContext<MetricsContext>()({
id: "metrics",
name: "Metrics Plugin",
setup: ({ options }) => ({
options: {
...options,
meta: { ...options.meta, startTime: Date.now() },
},
}),
hooks: {
onSuccess: ({ options }) => {
const duration = Date.now() - (options.meta?.startTime ?? Date.now());
console.info(`Request took ${duration}ms`);
},
},
middlewares: {
fetchMiddleware: (ctx) => async (input, init) => {
console.info("→", input);
const response = await ctx.fetchImpl(input, init);
console.info("←", response.status);
return response;
},
},
});
const api = createFetchClient({
plugins: [metricsPlugin],
});URL Helpers
Dynamic params, query strings, and method prefixes.
await callApi("/users/:id", { params: { id: 123 } });
await callApi("/search", { query: { q: "test" } });
await callApi("@delete/users/123");And so much more
See the full documentation for the full list of features.
Installation
npm install @zayne-labs/callapiimport { callApi, createFetchClient } from "@zayne-labs/callapi";
// Simple
const { data } = await callApi("/api/users");
// Configured
const api = createFetchClient({
baseURL: "https://api.example.com",
retryAttempts: 2,
timeout: 10000,
onError: ({ error }) => trackError(error),
});CDN
<script type="module">
import { callApi } from "https://esm.run/@zayne-labs/callapi@latest";
</script>What makes it worth considering?
- TypeScript-first - Full inference everywhere
- Familiar API - If you know fetch, you know CallApi
- Actually small - Zero dependencies and ~7KB (minified + brotli)
- Fast - Built on native Web APIs
- Works everywhere - Browsers, Node 18+, Deno, Bun, Cloudflare Workers
Coming from axios, ky or ofetch? See how CallApi compares.
License
MIT © Ryan Zayne
