Skip to Content
✨ New: Plugin Support. Extend the core functionality with ease. Read more... 🎉
Advanced Examples

Advanced Examples

Real-world, production-ready examples combining multiple features.

Full REST API Client

Complete CRUD operations with authentication, error handling, and organization:

import { createApiClient, group, get, post, put, patch, del } from 'endpoint-fetcher'; // Types type User = { id: string; name: string; email: string; role: 'admin' | 'user'; createdAt: string; }; type Post = { id: string; title: string; content: string; userId: string; published: boolean; createdAt: string; }; type Comment = { id: string; content: string; postId: string; userId: string; createdAt: string; }; type ApiError = { message: string; code: string; }; // Token management const getToken = () => localStorage.getItem('jwt'); const setToken = (token: string) => localStorage.setItem('jwt', token); const api = createApiClient({ // Public endpoints public: group({ endpoints: { health: get<void, { status: string }>('/health'), posts: get<void, Post[]>('/posts'), post: get<{ id: string }, Post>((input) => `/posts/${input.id}`), } }), // Authentication auth: group({ endpoints: { login: post<{ email: string; password: string }, { token: string; user: User }, ApiError>('/auth/login'), register: post<{ name: string; email: string; password: string }, { token: string; user: User }, ApiError>('/auth/register'), logout: post<void, void>('/auth/logout'), refresh: post<void, { token: string }>('/auth/refresh'), } }), // User endpoints (protected) users: group({ hooks: { beforeRequest: async (url, init) => { const token = getToken(); if (!token) throw new Error('Not authenticated'); return { url, init }; }, }, 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' | 'createdAt'>, Post, ApiError>('/users/me/posts'), update: patch<Partial<Post> & { id: string }, Post, ApiError>( (input) => `/users/me/posts/${input.id}` ), delete: del<{ id: string }, void, ApiError>((input) => `/users/me/posts/${input.id}`), } }), comments: group({ endpoints: { list: get<void, Comment[]>('/users/me/comments'), create: post<Omit<Comment, 'id' | 'userId' | 'createdAt'>, Comment, ApiError>('/users/me/comments'), delete: del<{ id: string }, void, ApiError>((input) => `/users/me/comments/${input.id}`), } }), } }), // Admin endpoints admin: group({ hooks: { beforeRequest: async (url, init) => { const token = getToken(); if (!token) throw new Error('Not authenticated'); return { url, init: { ...init, headers: { ...init.headers, 'X-Admin-Request': 'true', } } }; }, }, groups: { users: group({ endpoints: { list: get<{ page?: number; limit?: number }, { data: User[]; total: number }>( (input) => { const params = new URLSearchParams(); if (input?.page) params.set('page', input.page.toString()); if (input?.limit) params.set('limit', input.limit.toString()); return `/admin/users?${params}`; } ), get: get<{ id: string }, User, ApiError>((input) => `/admin/users/${input.id}`), ban: post<{ id: string; reason: string }, void, ApiError>( (input) => `/admin/users/${input.id}/ban` ), unban: post<{ id: string }, void, ApiError>((input) => `/admin/users/${input.id}/unban`), delete: del<{ id: string }, void, ApiError>((input) => `/admin/users/${input.id}`), } }), posts: group({ endpoints: { list: get<void, Post[]>('/admin/posts'), delete: del<{ id: string }, void, ApiError>((input) => `/admin/posts/${input.id}`), feature: post<{ id: string }, Post, ApiError>((input) => `/admin/posts/${input.id}/feature`), } }), } }), }, { baseUrl: import.meta.env.VITE_API_URL || 'https://api.example.com', defaultHeaders: { 'Content-Type': 'application/json', }, hooks: { // Add JWT to all requests beforeRequest: async (url, init) => { const token = getToken(); if (token && !url.includes('/auth/')) { return { url, init: { ...init, headers: { ...init.headers, Authorization: `Bearer ${token}`, } } }; } return { url, init }; }, // Handle 401 and refresh token afterResponse: async (response, url, init) => { if (response.status === 401 && !url.includes('/auth/')) { try { const { token } = await api.auth.refresh(); setToken(token); // Retry original request return fetch(url, { ...init, headers: { ...init.headers, Authorization: `Bearer ${token}`, }, }); } catch { // Refresh failed, redirect to login window.location.href = '/login'; throw new Error('Session expired'); } } return response; }, // Global error tracking onError: async (error) => { console.error('API Error:', error); // Send to error tracking service }, }, }); export default api;

Authentication Flow with Token Refresh

Complete authentication system with automatic token refresh:

import { createApiClient, group, post, get } from 'endpoint-fetcher'; type AuthTokens = { accessToken: string; refreshToken: string; expiresIn: number; }; type User = { id: string; email: string; name: string; }; class TokenManager { private accessToken: string | null = null; private refreshToken: string | null = null; private refreshPromise: Promise<string> | null = null; constructor() { this.loadTokens(); } private loadTokens() { this.accessToken = localStorage.getItem('accessToken'); this.refreshToken = localStorage.getItem('refreshToken'); } setTokens(tokens: AuthTokens) { this.accessToken = tokens.accessToken; this.refreshToken = tokens.refreshToken; localStorage.setItem('accessToken', tokens.accessToken); localStorage.setItem('refreshToken', tokens.refreshToken); } getAccessToken(): string | null { return this.accessToken; } async refresh(refreshFn: () => Promise<AuthTokens>): Promise<string> { // Prevent multiple simultaneous refresh calls if (this.refreshPromise) { return this.refreshPromise; } this.refreshPromise = (async () => { try { const tokens = await refreshFn(); this.setTokens(tokens); return tokens.accessToken; } finally { this.refreshPromise = null; } })(); return this.refreshPromise; } clear() { this.accessToken = null; this.refreshToken = null; localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); } } const tokenManager = new TokenManager(); const api = createApiClient({ auth: group({ endpoints: { login: post<{ email: string; password: string }, AuthTokens>('/auth/login'), register: post<{ email: string; password: string; name: string }, AuthTokens>('/auth/register'), logout: post<void, void>('/auth/logout'), } }), users: group({ endpoints: { me: get<void, User>('/users/me'), } }), }, { baseUrl: 'https://api.example.com', hooks: { beforeRequest: async (url, init) => { if (url.includes('/auth/login') || url.includes('/auth/register')) { return { url, init }; } const token = tokenManager.getAccessToken(); if (token) { 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/')) { // Token expired, try to refresh try { const newToken = await tokenManager.refresh(async () => { const refreshToken = localStorage.getItem('refreshToken'); const res = await fetch('https://api.example.com/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }), }); if (!res.ok) throw new Error('Refresh failed'); return res.json(); }); // Retry original request with new token return fetch(url, { ...init, headers: { ...init.headers, Authorization: `Bearer ${newToken}`, }, }); } catch { tokenManager.clear(); window.location.href = '/login'; throw new Error('Session expired'); } } return response; }, }, }); // Login helper export async function login(email: string, password: string) { const tokens = await api.auth.login({ email, password }); tokenManager.setTokens(tokens); return tokens; } // Logout helper export async function logout() { try { await api.auth.logout(); } finally { tokenManager.clear(); window.location.href = '/login'; } } export default api;

Multi-Environment Setup

Configure API client for different environments:

import { createApiClient, group, get, post } from 'endpoint-fetcher'; type Environment = 'development' | 'staging' | 'production'; const config = { development: { apiUrl: 'http://localhost:3000', enableLogging: true, enableMocking: true, }, staging: { apiUrl: 'https://staging-api.example.com', enableLogging: true, enableMocking: false, }, production: { apiUrl: 'https://api.example.com', enableLogging: false, enableMocking: false, }, }; const env = (import.meta.env.VITE_ENV || 'development') as Environment; const envConfig = config[env]; // Conditional logging plugin const loggingPlugin = () => { if (!envConfig.enableLogging) return { hooks: {} }; return { hooks: { beforeRequest: async (url: string, init: RequestInit) => { console.log(`→ ${init.method} ${url}`); return { url, init }; }, afterResponse: async (response: Response) => { console.log(`← ${response.status}`); return response; }, }, }; }; // Mock data for development const mockFetch: typeof fetch = async (input, init) => { if (!envConfig.enableMocking) { return fetch(input, init); } const url = typeof input === 'string' ? input : input.toString(); // Mock responses if (url.includes('/users')) { return new Response( JSON.stringify([ { id: '1', name: 'John Doe', email: 'john@example.com' }, ]), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } return fetch(input, init); }; const api = createApiClient({ users: group({ endpoints: { list: get<void, User[]>('/users'), create: post<CreateUserInput, User>('/users'), } }), }, { baseUrl: envConfig.apiUrl, fetch: mockFetch, plugins: [loggingPlugin()], defaultHeaders: { 'X-Environment': env, }, }); export default api;

React Query Integration

Use endpoint-fetcher with React Query:

import { createApiClient, group, get, post, patch, del } from 'endpoint-fetcher'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; // API Client 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 keys const userKeys = { all: ['users'] as const, lists: () => [...userKeys.all, 'list'] as const, list: (filters: string) => [...userKeys.lists(), { filters }] as const, details: () => [...userKeys.all, 'detail'] as const, detail: (id: string) => [...userKeys.details(), id] as const, }; // Hooks export function useUsers() { return useQuery({ queryKey: userKeys.lists(), queryFn: () => api.users.list(), }); } export function useUser(id: string) { return useQuery({ queryKey: userKeys.detail(id), queryFn: () => api.users.getById({ id }), enabled: !!id, }); } export function useCreateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateUserInput) => api.users.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: userKeys.lists() }); }, }); } export function useUpdateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: UpdateUserInput) => api.users.update(data), onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: userKeys.detail(variables.id) }); queryClient.invalidateQueries({ queryKey: userKeys.lists() }); }, }); } export function useDeleteUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => api.users.delete({ id }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: userKeys.lists() }); }, }); } // Usage in components function UsersList() { const { data: users, isLoading } = useUsers(); const createUser = useCreateUser(); const deleteUser = useDeleteUser(); if (isLoading) return <div>Loading...</div>; return ( <div> {users?.map(user => ( <div key={user.id}> {user.name} <button onClick={() => deleteUser.mutate(user.id)}>Delete</button> </div> ))} <button onClick={() => createUser.mutate({ name: 'New User', email: 'new@example.com' })}> Add User </button> </div> ); }

SWR Integration

Use endpoint-fetcher with SWR:

import { createApiClient, group, get } from 'endpoint-fetcher'; import useSWR from 'swr'; const api = createApiClient({ users: group({ endpoints: { list: get<void, User[]>('/users'), getById: get<{ id: string }, User>((input) => `/users/${input.id}`), } }), }, { baseUrl: 'https://api.example.com', }); // Hooks export function useUsers() { return useSWR('/users', () => api.users.list()); } export function useUser(id: string | null) { return useSWR( id ? `/users/${id}` : null, () => api.users.getById({ id: id! }) ); } // Usage function UserProfile({ userId }: { userId: string }) { const { data: user, error, isLoading } = useUser(userId); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!user) return null; return <div>{user.name}</div>; }

Complex Hook Composition

Combine multiple hooks for sophisticated behavior:

import { createApiClient, group, get, post } from 'endpoint-fetcher'; // Request ID tracking const requestIdPlugin = () => ({ hooks: { beforeRequest: async (url: string, init: RequestInit) => { return { url, init: { ...init, headers: { ...init.headers, 'X-Request-ID': crypto.randomUUID(), } } }; }, }, }); // Performance monitoring const performancePlugin = () => { const timings = new Map<string, number>(); return { hooks: { beforeRequest: async (url: string, init: RequestInit) => { const key = `${init.method}:${url}`; timings.set(key, Date.now()); return { url, init }; }, afterResponse: async (response: Response, url: string, init: RequestInit) => { const key = `${init.method}:${url}`; const start = timings.get(key); if (start) { const duration = Date.now() - start; console.log(`${key}: ${duration}ms`); timings.delete(key); // Send to analytics if (duration > 1000) { console.warn(`Slow request: ${key}`); } } return response; }, }, }; }; // Rate limiting per endpoint const rateLimitPlugin = () => { const limits = new Map<string, { count: number; resetAt: number }>(); return { hooks: { beforeRequest: async (url: string, init: RequestInit) => { const key = `${init.method}:${url}`; const now = Date.now(); const limit = limits.get(key); if (limit && limit.resetAt > now) { if (limit.count >= 10) { throw new Error('Rate limit exceeded'); } limit.count++; } else { limits.set(key, { count: 1, resetAt: now + 60000 }); } return { url, init }; }, }, }; }; const api = createApiClient({ users: group({ endpoints: { list: get<void, User[]>('/users'), } }), }, { baseUrl: 'https://api.example.com', plugins: [ requestIdPlugin(), performancePlugin(), rateLimitPlugin(), ], hooks: { beforeRequest: async (url, init) => { // Global auth const token = getToken(); return { url, init: { ...init, headers: { ...init.headers, Authorization: `Bearer ${token}`, } } }; }, }, });

Pagination Pattern

Handle paginated responses:

type PaginatedResponse<T> = { data: T[]; pagination: { page: number; pageSize: number; total: number; totalPages: number; }; }; const api = createApiClient({ users: group({ endpoints: { list: get<{ page?: number; limit?: number }, PaginatedResponse<User>>( (input) => { const params = new URLSearchParams(); if (input?.page) params.set('page', input.page.toString()); if (input?.limit) params.set('limit', input.limit.toString()); return `/users?${params}`; } ), } }), }, { baseUrl: 'https://api.example.com', }); // Helper for infinite scroll async function* fetchAllUsers() { let page = 1; let hasMore = true; while (hasMore) { const response = await api.users.list({ page, limit: 50 }); yield response.data; hasMore = page < response.pagination.totalPages; page++; } } // Usage for await (const users of fetchAllUsers()) { console.log(`Fetched ${users.length} users`); // Process batch }

Server-Side Usage (Node.js)

Use endpoint-fetcher in Node.js:

import { createApiClient, group, get, post } from 'endpoint-fetcher'; import fetch from 'node-fetch'; const api = createApiClient({ users: group({ endpoints: { list: get<void, User[]>('/users'), create: post<CreateUserInput, User>('/users'), } }), }, { baseUrl: process.env.API_URL || 'https://api.example.com', fetch: fetch as any, // Use node-fetch defaultHeaders: { 'User-Agent': 'MyApp/1.0', }, }); // Server-side function export async function getUsersForSSR() { try { const users = await api.users.list(); return { users, error: null }; } catch (error) { console.error('Failed to fetch users:', error); return { users: [], error: 'Failed to load users' }; } }