Comparisons

How is CallApi different from other existing fetching libraries?

CallApi is a modern fetch wrapper designed to solve real problems. Here's how it compares to other popular libraries.

Philosophy

CallApi is built on four core principles:

  1. Lightweight: Under 6KB. Zero dependencies. Pure ESM.

  2. Simple: Based on Fetch API, mirrors its interface 1-to-1. Only adds useful features with sensible defaults.

  3. Type-safe: Full TypeScript support with type inference via schemas, validators, and manual generics.

  4. Extensible: Plugins and hooks let you add or modify features without changing core code.

CallApi vs Axios

Overview

Axios has been fundamental to web development for years. However, modern web development has evolved, and CallApi addresses many of Axios's limitations.

Key Differences

Axios:

  • Based on XMLHttpRequest (legacy API)
  • Custom API design
  • Verbose configuration for some use cases
  • Requires adapters for different environments

CallApi:

  • Based on modern Fetch API
  • Drop-in replacement for fetch
  • Concise, intuitive API
  • Works everywhere (browsers, Node 18+, Deno, Bun, Workers)

Features Comparison:

FeatureAxiosCallApi
Request/Response Interceptors✅ (Hooks & Plugins)
Automatic JSON Parsing✅ (Content-Type aware)
Timeout Support
Request Cancellation✅ (Built-in)
Progress Events✅ (Streaming)
Retry Logic✅ (Exponential backoff)
Request Deduplication
Schema Validation✅ (Any Standard Schema)
TypeScript Type Inference
Plugin System
URL Helpers✅ (Params, query, prefixes)

Size Comparison:

  • Axios: ~13KB minified + gzipped
  • CallApi: <6KB minified + gzipped

CallApi is over 50% smaller while providing more features.

Migration Example

import axios from "axios";

const api = axios.create({
	baseURL: "https://api.example.com",
	timeout: 10000,
	headers: {
		"Content-Type": "application/json",
	},
});

// Add auth token
api.interceptors.request.use((config) => {
	config.headers.Authorization = `Bearer ${getToken()}`;
	return config;
});

// Handle errors
api.interceptors.response.use(
	(response) => response,
	(error) => {
		if (error.response?.status === 401) {
			redirectToLogin();
		}
		return Promise.reject(error);
	}
);

try {
	const response = await api.get("/users");
	const users = response.data;
} catch (error) {
	if (axios.isAxiosError(error)) {
		console.error(error.response?.data);
	}
}
import { createFetchClient } from "@zayne-labs/callapi";

const api = createFetchClient({
	baseURL: "https://api.example.com",
	timeout: 10000,

	// Add auth token
	onRequest: ({ request }) => {
		options.auth = getToken(); // Or request.headers["Authorization"] = `Bearer ${getToken()}`;
	},

	// Handle errors
	onError: ({ error, response }) => {
		if (response?.status === 401) {
			redirectToLogin();
		}
	},
});

const { data: users, error } = await api("/users");

if (error) {
	console.error(error.errorData);
}

When to Choose Axios

  • You need IE11 support
  • Your team is deeply familiar with Axios and migration cost is high
  • You rely on Axios-specific ecosystem packages

When to Choose CallApi

  • You want modern, fetch-based API
  • Bundle size matters
  • You need TypeScript type inference
  • You want built-in retries and deduplication
  • You need schema validation
  • You're starting a new project

CallApi vs Ky

Overview

Ky is a popular modern fetch wrapper with a focus on simplicity and developer experience.

Key Differences

FeatureKyCallApi
Fetch-based
Timeout Support
Retry Logic✅ (More flexible)
Hooks✅ (More comprehensive)
JSON Handling✅ (Content-Type aware)
Bundle Size~5KB<6KB
FeatureKyCallApi
Request Deduplication✅ (3 strategies)
Schema Validation
Type Inference⚠️ Limited✅ Full
Plugin System
Streaming Progress
URL Helpers
Method Prefixes

Ky:

import ky from "ky";

type User = { id: number; name: string };

const user = await ky.get("api/users/1").json<User>();

CallApi:

import { callApi } from "@zayne-labs/callapi";
import { z } from "zod";

const userSchema = z.object({
	id: z.number(),
	name: z.string(),
});

const { data: user } = await callApi("api/users/1", {
	schema: { data: userSchema },
	// Type automatically inferred!
});

When to Choose Ky

  • You want a minimal API with no extra features
  • You don't need schema validation or type inference
  • You prefer chaining API style

When to Choose CallApi

  • You need request deduplication
  • You want schema validation with type inference
  • You need a plugin system for extensibility
  • You want URL helpers and parameter substitution
  • You need streaming progress tracking

CallApi vs Ofetch

Overview

CallApi is highly inspired by Ofetch. Both share similar philosophies and design patterns.

Key Differences

Both libraries share:

  • Fetch API foundation
  • Automatic JSON handling
  • Retry support with exponential backoff
  • Lifecycle hooks/interceptors
  • Object literal request body support
  • Timeout support
  • TypeScript-first design

CallApi adds:

FeatureOfetchCallApi
Request Deduplication✅ (3 strategies)
Schema Validation✅ (Standard Schema)
Type Inference⚠️ Limited✅ Full
Plugin System
Streaming Progress
URL Params Substitution
Method Prefixes✅ (@get, @post, etc.)
Result Modes✅ (onlyData, onlyError, etc.)
Structured Errors⚠️ Basic✅ Full (HTTPError, ValidationError)

Size Comparison:

  • Ofetch: ~8KB minified + gzipped
  • CallApi: <6KB minified + gzipped

CallApi is smaller while providing more features.

Code Comparison

Ofetch:

import { ofetch } from "ofetch";

const api = ofetch.create({
	baseURL: "https://api.example.com",
	onRequest: ({ options }) => {
		options.headers = {
			...options.headers,
			Authorization: `Bearer ${token}`,
		};
	},
});

const users = await api("/users");

CallApi:

import { createFetchClient } from "@zayne-labs/callapi";

const api = createFetchClient({
	baseURL: "https://api.example.com",
	onRequest: ({ request, options }) => {
		options.auth = token; // Or request.headers["Authorization"] = `Bearer ${token}`;
	},
});

const { data: users } = await api("/users");

Ofetch:

import { ofetch } from "ofetch";
import { z } from "zod";

const userSchema = z.object({
	id: z.number(),
	name: z.string(),
});

// Manual validation required
const data = await ofetch("/users/1");
const user = userSchema.parse(data);

CallApi:

import { callApi } from "@zayne-labs/callapi";
import { z } from "zod";

const userSchema = z.object({
	id: z.number(),
	name: z.string(),
});

// Automatic validation + type inference
const { data: user } = await callApi("/users/1", {
	schema: { data: userSchema },
});
// Type inferred as z.infer<typeof userSchema>

Ofetch:

// No built-in plugin system
// Need to manually compose functionality

CallApi:

import { createFetchClient } from "@zayne-labs/callapi";
import { definePlugin } from "@zayne-labs/callapi/utils";

const metricsPlugin = definePlugin({
	id: "metrics",
	name: "Metrics Plugin",

	setup: ({ options }) => ({
		options: {
			...options,
			meta: { startTime: Date.now() },
		},
	}),

	hooks: {
		onSuccess: ({ options }) => {
			const duration = Date.now() - options.meta.startTime;
			console.log(`Request took ${duration}ms`);
		},
	},
});

const api = createFetchClient({
	plugins: [metricsPlugin],
});

When to Choose Ofetch

  • You need a proven, battle-tested library
  • You're already using it and don't need extra features
  • You prefer a slightly simpler API

When to Choose CallApi

  • You need request deduplication
  • You want built-in schema validation and type inference
  • You need a plugin system
  • You want streaming progress tracking
  • You need URL parameter substitution
  • You want method prefixes (@get, @post)
  • Smaller bundle size matters

Feature Matrix

Comprehensive comparison of all libraries:

FeatureAxiosKyOfetchCallApi
Core
Fetch-based
Zero Dependencies
Bundle Size13KB5KB8KB<6KB
Request Features
Timeout
Retry Logic
Exponential Backoff⚠️
Request Cancellation
Request Deduplication
URL Param Substitution
Method Prefixes
Response Features
Auto JSON Parsing
Content-Type Detection⚠️
Schema Validation
Streaming Progress
Developer Experience
TypeScript Support⚠️
Type Inference⚠️⚠️
Hooks/Interceptors
Plugin System
Error Handling⚠️

✅ = Full support | ⚠️ = Partial support | ❌ = Not supported

Migration Paths

For detailed migration guides from each library, see:

Summary

CallApi is designed for modern web development:

Choose CallApi if you want:

  • Modern, fetch-based API
  • Built-in schema validation with type inference
  • Request deduplication for performance
  • Extensible plugin system
  • Comprehensive TypeScript support
  • Small bundle size (<6KB)
  • All the convenience features you need

Choose other libraries if:

  • Axios: You need IE11 support or have heavy Axios investment
  • Ky: You want absolute minimal API surface
  • Ofetch: You're already using it and satisfied

CallApi provides the best balance of features, bundle size, and developer experience for modern applications.

Edit on GitHub

Last updated on

On this page