A powerful TypeScript wrapper for native Fetch API that provides enhanced features including token management, interceptors, and type-safe HTTP requests. Perfect for both client-side and server-side applications.
- 🔒 Built-in token management - Automatic access & refresh token handling
- 🎯 Type-safe HTTP requests - Full TypeScript support with generics
- 🔄 Automatic token refresh - Seamless token renewal with request queuing
- 🎨 Customizable interceptors - Request/response/error interceptors
- 🛠 Flexible configuration - Per-API and global configuration options
- 📝 Content-type management - Support for JSON, form-data, and more
- 🌐 Native Fetch API - Smaller bundle size, no external dependencies
- ⚡ Zero dependencies - Lightweight and fast
- 🚀 Axios-compatible - Easy migration from Axios
- 🍪 Cookie support - Built-in cookie management utilities
- 🔧 Server-side ready - Works in Node.js environments
- 🧹 Smart parameter filtering - Automatic removal of undefined/null/empty values
- 🎭 Intelligent error handling - 400-level errors return as responses, 500-level errors throw
- 📦 ES Modules support - Full compatibility with Vite, Node.js, and modern bundlers
npm install api-wizard
# or
yarn add api-wizardimport { handler } from 'api-wizard';
// Define API endpoints configuration
const apiConfig = {
users: 'https://api.users.com',
products: 'https://api.products.com'
};
// Create API handlers
const api = handler(apiConfig);
// Make type-safe requests
interface User {
id: number;
name: string;
email: string;
}
// GET request with version
const userApi = api.users({ version: 'v1' }); // https://api.users.com/v1
const getUser = async (id: number) => {
const response = await userApi.get<User>(`/users/${id}`);
return response.data;
};
// POST request with type-safe request body
interface CreateUserRequest {
name: string;
email: string;
}
const createUser = async (userData: CreateUserRequest) => {
const response = await userApi.post<CreateUserRequest, User>('/users', userData);
return response.data;
};import instance from 'api-wizard';
// Create a direct instance
const api = instance('https://api.example.com', {
version: 'v1',
contentType: 'application/json'
});
// Use directly
const response = await api.get<User[]>('/users');
const newUser = await api.post<CreateUserRequest, User>('/users', userData);const userApi = api.users({
version: 'v1',
interceptor: {
tokenConfig: {
// Token storage configuration
getToken: () => localStorage.getItem('access_token'),
setToken: (token) => localStorage.setItem('access_token', token),
removeToken: () => localStorage.removeItem('access_token'),
// Refresh token configuration
getRefreshToken: () => localStorage.getItem('refresh_token'),
setRefreshToken: (token) => localStorage.setItem('refresh_token', token),
removeRefreshToken: () => localStorage.removeItem('refresh_token'),
// Endpoints
refreshEndpoint: '/auth/refresh',
// Optional: Custom header format
formatAuthHeader: (token) => ({
'Authorization': `Custom ${token}`
}),
// Optional: Token expiry callback
onTokenExpired: () => {
// Handle token expiration (e.g., redirect to login)
}
}
}
});const userApi = api.users({
version: 'v1',
interceptor: {
onRequest: (config) => {
// Modify request config
config.headers['Custom-Header'] = 'value';
return config;
},
onResponse: (response) => {
// Transform response data
response.data = response.data.result;
return response;
},
onError: async (error) => {
// Custom error handling
if (error.response?.status === 404) {
// Handle 404 error
}
return Promise.reject(error);
}
}
});const userApi = api.users({
// API version will be added to base URL
version: 'v1',
// Content type configuration
contentType: 'application/json',
charset: 'UTF-8',
accept: 'application/json',
// Credentials
withCredentials: true // default: true
});
// Results in: https://api.example.com/v1
// Headers:
// Content-Type: application/json; charset=UTF-8
// Accept: application/json
// Credentials: includeinterface Option {
version?: string;
contentType?: DataType;
accept?: DataType;
charset?: string;
interceptor?: Interceptor;
withCredentials?: boolean;
requestConfig?: FetchRequestConfig;
}interface TokenConfig {
accessTokenKey?: string;
refreshTokenKey?: string;
refreshEndpoint?: string;
getToken?: () => string | undefined;
setToken?: (token: string) => void;
removeToken?: () => void;
getRefreshToken?: () => string | undefined;
setRefreshToken?: (token: string) => void;
removeRefreshToken?: () => void;
onTokenExpired?: () => void;
formatAuthHeader?: (token: string, refreshToken?: string) => Record<string, string>;
}type DataType =
| "application/json"
| "application/x-www-form-urlencoded"
| "application/xml"
| "application/octet-stream"
| "multipart/form-data"
| "text/plain"
| "text/html";API Wizard automatically filters out undefined, null, and empty string values from query parameters:
// Before: ?page=undefined&limit=10&search=
// After: ?limit=10
const response = await api.get('/users', {
params: {
page: undefined, // Automatically removed
limit: 10, // Kept and converted to string
search: '', // Automatically removed
status: null // Automatically removed
}
});API Wizard provides intelligent error handling that distinguishes between client and server errors:
// 400-level errors (client errors) are returned as normal responses
const response = await api.get('/users/999');
if (!response.ok) {
console.log('Client error:', response.status); // 404
console.log('Error data:', response.data); // Error details
}
// 500-level errors (server errors) throw exceptions
try {
const response = await api.get('/server-error');
} catch (error) {
console.error('Server error:', error.message);
// Handle server error
}API Wizard automatically handles token refresh when tokens expire:
- Detects 401 responses
- Automatically calls refresh endpoint
- Retries original request with new token
- Handles concurrent requests during refresh
Full TypeScript support with generic types:
// Request and response types are fully typed
const response = await api.post<CreateUserRequest, User>('/users', userData);
// response.data is typed as UserConfigure different options for different APIs:
const api = handler({
users: 'https://api.users.com',
payments: 'https://api.payments.com'
}, {
// Global configuration for all APIs
withCredentials: true,
contentType: 'application/json'
});
// API-specific configuration
const usersApi = api.users({
version: 'v1',
interceptor: { /* user-specific interceptors */ }
});
const paymentsApi = api.payments({
version: 'v2',
contentType: 'application/xml'
});All standard HTTP methods are supported with full TypeScript support:
const api = instance('https://api.example.com', { version: 'v1' });
// GET - Retrieve data
const users = await api.get<User[]>('/users');
const user = await api.get<User>(`/users/${id}`);
// POST - Create new resource
const newUser = await api.post<CreateUserRequest, User>('/users', userData);
// PUT - Update entire resource
const updatedUser = await api.put<UpdateUserRequest, User>(`/users/${id}`, userData);
// PATCH - Partial update
const patchedUser = await api.patch<Partial<User>, User>(`/users/${id}`, partialData);
// DELETE - Remove resource
await api.delete(`/users/${id}`);
// Request with custom config
const response = await api.get<User[]>('/users', {
params: { page: '1', limit: '10' },
timeout: 5000,
headers: { 'Custom-Header': 'value' }
});interface FetchRequestConfig extends RequestInit {
params?: Record<string, string>; // Query parameters
timeout?: number; // Request timeout in ms
baseURL?: string; // Override base URL
}API Wizard includes built-in cookie utilities for both client-side and server-side applications:
import { getCookies } from 'api-wizard';
// Automatically reads from document.cookie
const cookies = getCookies();
console.log(cookies); // "sessionId=abc123; userId=456; theme=dark"import { getCookies, CookieConfig } from 'api-wizard';
// Set cookies for server-side usage
new CookieConfig('sessionId=abc123; userId=456; theme=dark');
// Get cookies (returns the configured cookies)
const cookies = getCookies();
console.log(cookies); // "sessionId=abc123; userId=456; theme=dark"import { getCookies, CookieConfig } from 'api-wizard';
import { handler } from 'api-wizard';
import express from 'express';
const app = express();
// API configuration
const apiConfig = {
sso: 'https://api.sso.com',
users: 'https://api.users.com'
};
const api = handler(apiConfig);
// Express route with cookie forwarding
app.get('/api/sso/sign', async (req, res) => {
try {
// 클라이언트의 쿠키를 전역으로 설정
new CookieConfig(req.headers.cookie || '');
if (req.headers.cookie) {
console.log('Received cookies:', req.headers.cookie);
}
// API 호출 (쿠키가 자동으로 포함됨)
const response = await ssoApi.sign.get();
if (response.status > 299) {
return res.status(401).json("Unauthorized");
}
res.json(response);
} catch (error) {
console.error('SSO Sign API Error:', error);
res.status(500).json({
error: 'Failed to fetch sign data',
message: error instanceof Error ? error.message : 'Unknown error'
});
}
});// services/ssoService.ts
import { getCookies, CookieConfig } from 'api-wizard';
import { handler } from 'api-wizard';
const apiConfig = {
sso: 'https://api.sso.com'
};
const api = handler(apiConfig);
interface SignApi {
userId: string;
userName: string;
isAuthenticated: boolean;
permissions: string[];
}
// SSO API 서비스
export const ssoApi = {
sign: {
async get(): Promise<FetchResponse<SignApi>> {
const api = api.sso({
version: "v3",
interceptor: {
onRequest: (config) => {
const cookies = getCookies();
if (cookies) {
config.headers = {
...config.headers,
'Cookie': cookies
};
}
return config;
}
}
});
return await api.get<SignApi>("/sign");
}
}
};// middleware/cookieMiddleware.ts
import { CookieConfig } from 'api-wizard';
export const cookieMiddleware = (req: Request, res: Response, next: NextFunction) => {
// 모든 요청에서 쿠키를 전역으로 설정
if (req.headers.cookie) {
new CookieConfig(req.headers.cookie);
console.log('Cookies configured:', req.headers.cookie);
}
next();
};
// app.ts
app.use(cookieMiddleware);// Parse cookies into an object
function parseCookies(cookieString: string): Record<string, string> {
return cookieString
.split(';')
.reduce((cookies, cookie) => {
const [name, value] = cookie.trim().split('=');
cookies[name] = value;
return cookies;
}, {} as Record<string, string>);
}
// Usage example
const cookieString = "sessionId=abc123; userId=456; theme=dark";
const cookieObj = parseCookies(cookieString);
console.log(cookieObj.sessionId); // "abc123"
console.log(cookieObj.userId); // "456"API Wizard automatically handles request bodies based on Content-Type headers:
// JSON body (default)
const response = await api.post('/users', userData);
// Body: JSON.stringify(userData)
// Form URL-encoded body
const response = await api.post('/login', formData, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
// Body: URLSearchParams(formData).toString()
// Custom body handling
const response = await api.post('/upload', fileData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
// Body: FormData object- Zero Dependencies: No external libraries required
- Small Bundle Size: Lightweight implementation (~15KB minified)
- Native Fetch: Uses browser's native HTTP client
- Tree Shaking: Only import what you need
- Modern JavaScript: Built for modern browsers and Node.js
- TypeScript First: Built with TypeScript from the ground up
- ES Modules: Full ESM support for modern bundlers
- Universal Compatibility: Works seamlessly in Vite, Webpack, Node.js, and Express
API Wizard is designed to be a drop-in replacement for Axios with minimal changes:
// Before (Axios)
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
headers: { 'Authorization': 'Bearer token' }
});
const response = await api.get('/users');
// After (API Wizard)
import instance from 'api-wizard';
const api = instance('https://api.example.com', {
interceptor: {
tokenConfig: {
getToken: () => 'token',
formatAuthHeader: (token) => ({ 'Authorization': `Bearer ${token}` })
}
}
});
const response = await api.get('/users');Here's a complete example showing how to use API Wizard in a React application:
// api.ts
import { handler } from 'api-wizard';
const apiConfig = {
users: 'https://api.users.com',
products: 'https://api.products.com'
};
export const api = handler(apiConfig);
// services/userService.ts
import { api } from '../api';
export interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
export interface CreateUserRequest {
name: string;
email: string;
}
export const userService = {
getUsers: () => api.users({ version: 'v1' }).get<User[]>('/users'),
getUser: (id: number) => api.users({ version: 'v1' }).get<User>(`/users/${id}`),
createUser: (user: CreateUserRequest) =>
api.users({ version: 'v1' }).post<CreateUserRequest, User>('/users', user),
updateUser: (id: number, user: User) =>
api.users({ version: 'v1' }).put<User, User>(`/users/${id}`, user),
deleteUser: (id: number) =>
api.users({ version: 'v1' }).delete(`/users/${id}`)
};Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT © Min-Hyeong Park