Examples
Real-world examples combining multiple features.
Full REST API Client
A production-ready client with authentication, token refresh, grouped endpoints, and error tracking:
import { createApiClient, group, get, post, patch, del } from 'endpoint-fetcher';
type User = { id: string; name: string; email: string; role: 'admin' | 'user' };
type Post = { id: string; title: string; content: string; userId: string };
type ApiError = { message: string; code: string };
const getToken = () => localStorage.getItem('jwt');
const setToken = (t: string) => localStorage.setItem('jwt', t);
const api = createApiClient({
auth: group({
endpoints: {
login: post<{ email: string; password: string }, { token: string; user: User }, ApiError>('/auth/login'),
logout: post<void, void>('/auth/logout'),
refresh: post<void, { token: string }>('/auth/refresh'),
},
}),
users: group({
endpoints: {
me: get<void, User, ApiError>('/users/me'),
updateMe: patch<Partial<User>, User, ApiError>('/users/me'),
},
groups: {
posts: group({
endpoints: {
list: get<void, Post[]>('/users/me/posts'),
create: post<Omit<Post, 'id' | 'userId'>, Post, ApiError>('/users/me/posts'),
delete: del<{ id: string }, void, ApiError>((input) => `/users/me/posts/${input.id}`),
},
}),
},
}),
admin: group({
hooks: {
beforeRequest: async (url, init) => ({
url,
init: { ...init, headers: { ...init.headers, 'X-Admin-Request': 'true' } },
}),
},
groups: {
users: group({
endpoints: {
list: get<{ page?: number }, { data: User[]; total: number }>((input) => {
const params = new URLSearchParams();
if (input?.page) params.set('page', input.page.toString());
return `/admin/users?${params}`;
}),
ban: post<{ id: string; reason: string }, void, ApiError>((input) => `/admin/users/${input.id}/ban`),
},
}),
},
}),
}, {
baseUrl: import.meta.env.VITE_API_URL || 'https://api.example.com',
defaultHeaders: { 'Content-Type': 'application/json' },
hooks: {
beforeRequest: async (url, init) => {
const token = getToken();
if (token && !url.includes('/auth/login')) {
return { url, init: { ...init, headers: { ...init.headers, Authorization: `Bearer ${token}` } } };
}
return { url, init };
},
afterResponse: async (response, url, init) => {
if (response.status === 401 && !url.includes('/auth/')) {
try {
const { token } = await api.auth.refresh();
setToken(token);
return fetch(url, { ...init, headers: { ...init.headers, Authorization: `Bearer ${token}` } });
} catch {
window.location.href = '/login';
throw new Error('Session expired');
}
}
return response;
},
onError: async (error) => {
console.error('API Error:', error);
},
},
});
export default api;React Query Integration
import { createApiClient, group, get, post, patch, del } from 'endpoint-fetcher';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
const api = createApiClient({
users: group({
endpoints: {
list: get<void, User[]>('/users'),
getById: get<{ id: string }, User>((input) => `/users/${input.id}`),
create: post<CreateUserInput, User>('/users'),
update: patch<UpdateUserInput, User>((input) => `/users/${input.id}`),
delete: del<{ id: string }, void>((input) => `/users/${input.id}`),
},
}),
}, { baseUrl: 'https://api.example.com' });
// Query key factory
const userKeys = {
all: () => ['users'] as const,
detail: (id: string) => ['users', id] as const,
};
export function useUsers() {
return useQuery({ queryKey: userKeys.all(), queryFn: () => api.users.list() });
}
export function useUser(id: string) {
return useQuery({ queryKey: userKeys.detail(id), queryFn: () => api.users.getById({ id }) });
}
export function useCreateUser() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreateUserInput) => api.users.create(data),
onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.all() }),
});
}Testing with a Mock Fetch
const mockFetch: typeof fetch = async (input, init) => {
const url = typeof input === 'string' ? input : input.toString();
if (url.includes('/users') && init?.method === 'GET') {
return new Response(
JSON.stringify([{ id: '1', name: 'Test User', email: 'test@example.com' }]),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
}
return new Response(JSON.stringify({ message: 'Not found' }), { status: 404 });
};
const api = createApiClient({
users: group({ endpoints: { list: get<void, User[]>('/users') } }),
}, {
baseUrl: 'https://api.example.com',
fetch: mockFetch,
});
const users = await api.users.list();
// users → [{ id: '1', name: 'Test User', ... }]