Fetchit Examples
Practical examples showing common use cases and patterns with the new separate client architecture.
💡 Complete Applications
These are complete application examples. For API reference and basic usage, see Usage Guide.
Table of Contents
- Basic CRUD Operations with HTTP Client
- Authentication
- Query Client
- Caching Strategies
- Error Handling
- Advanced Patterns
Basic CRUD Operations with HTTP Client
GET Request
ts
import { createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({
baseUrl: 'https://api.example.com',
});
interface User {
id: string;
name: string;
email: string;
}
// Returns data directly
const user = await http.get<User>('/users/1');
console.log(user.name); // Direct access
console.log(user.email);POST Request
ts
const user = await http.post<User>('/users', {
body: {
name: 'Alice',
email: 'alice@example.com',
},
});
console.log('Created user:', user); // Direct accessPUT Request
ts
const user = await http.put<User>('/users/1', {
body: {
name: 'Alice Smith',
email: 'alice.smith@example.com',
},
});
console.log('Updated user:', user);PATCH Request
ts
const user = await http.patch<User>('/users/1', {
body: { email: 'newemail@example.com' },
});
console.log('Updated email:', user.email);DELETE Request
ts
await http.delete('/users/1');
// Returns void or deletion confirmationAuthentication
🔐 Security Best Practices
- Never store tokens in localStorage (use httpOnly cookies when possible)
- Implement token refresh logic
- Clear cache on logout to prevent data leaks
- Always validate tokens on the server-side
Setting Auth Headers
ts
const http = createHttpClient({
baseUrl: 'https://api.example.com',
});
// After login
function login(token: string) {
http.setHeaders({
Authorization: `Bearer ${token}`,
});
}
// On logout
function logout() {
http.setHeaders({
Authorization: undefined, // Removes the header
});
queryClient.clearCache(); // Clear cached authenticated data
}Auth Token Refresh
ts
import { HttpError, createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
async function apiRequest<T>(method: 'get' | 'post' | 'put' | 'delete', url: string, options?: any): Promise<T> {
try {
return await http[method]<T>(url, options);
} catch (error) {
if (error instanceof HttpError && error.status === 401) {
// Token expired, refresh it
const newToken = await refreshAuthToken();
http.setHeaders({ Authorization: `Bearer ${newToken}` });
// Retry the request
return await http[method]<T>(url, options);
}
throw error;
}
}Cache Management
Query Keys
ts
import { createHttpClient, createQueryClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const queryClient = createQueryClient();
// Use query keys for better cache control
const user = await queryClient.fetch({
queryKey: ['users', '1'],
queryFn: () => http.get<User>('/users/1'),
});
// Later, invalidate this specific query
queryClient.invalidate(['users', '1']);Request Deduplication
ts
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
// HTTP client automatically deduplicates identical concurrent requests
const [user1, user2, user3] = await Promise.all([
http.get<User>('/users/1'),
http.get<User>('/users/1'),
http.get<User>('/users/1'),
]);
console.log(user1 === user2); // true (same instance)Force Refresh
ts
// Invalidate cache first, then fetch fresh data
queryClient.invalidate(['users', '1']);
const freshUser = await queryClient.fetch({
queryKey: ['users', '1'],
queryFn: () => http.get<User>('/users/1'),
});Cache Cleanup
ts
// Remove all cached queries
queryClient.clearCache();
// Get cache size
const size = queryClient.getCacheSize();
console.log(`Cache has ${size} queries`);
// Invalidate specific queries
queryClient.invalidate(['users', '1']);
// Invalidate all user queries (pattern matching)
queryClient.invalidate(['users']);
// Check cache size
console.log(`Cache contains ${size} entries`);URL Building
Query Parameters
ts
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
// Use params option for query parameters
const users = await http.get<User[]>('/api/users', {
params: {
page: 1,
limit: 10,
sort: 'name',
active: true,
},
});
// Actual request: "/api/users?page=1&limit=10&sort=name&active=true"Dynamic URLs
ts
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
function getUser(id: string) {
return http.get<User>(`/users/${id}`);
}
function searchUsers(query: string, page: number) {
return http.get<User[]>('/users/search', {
params: { q: query, page },
});
}File Uploads
Single File Upload
ts
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const fileInput = document.querySelector<HTMLInputElement>('#file');
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('description', 'Profile picture');
await http.post('/upload', {
body: formData,
// Content-Type is set automatically
});Multiple Files
ts
const formData = new FormData();
for (const file of files) {
formData.append('files[]', file);
}
await http.post('/upload/multiple', {
body: formData,
});Progress Tracking
ts
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
const percent = (e.loaded / e.total) * 100;
console.log(`Upload progress: ${percent}%`);
});
// For progress tracking, you may need to use XMLHttpRequest
// Fetchit focuses on simplicity over advanced upload featuresError Handling
Basic Error Handling
ts
import { HttpError, createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
try {
const user = await http.get<User>('/users/1');
console.log(user);
} catch (error) {
if (error instanceof HttpError) {
console.error(`${error.method} ${error.url} failed`);
console.error(`Status: ${error.status}`);
console.error(`Message: ${error.message}`);
} else {
console.error('Network error:', error);
}
}Status Code Handling
ts
import { HttpError } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
try {
await http.get('/users/1');
} catch (error) {
if (error instanceof HttpError) {
switch (error.status) {
case 404:
console.error('User not found');
break;
case 401:
console.error('Not authenticated');
redirectToLogin();
break;
case 403:
console.error('No permission');
break;
default:
console.error('Request failed');
}
}
}Global Error Handler
ts
import { HttpError, createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
async function safeRequest<T>(requestFn: () => Promise<T>): Promise<T | null> {
try {
return await requestFn();
} catch (error) {
if (error instanceof HttpError) {
// Log to error tracking service
trackError({
url: error.url,
method: error.method,
status: error.status,
message: error.message,
});
// Show user-friendly message
showNotification('Something went wrong. Please try again.');
}
return null;
}
}
// Usage
const user = await safeRequest(() => http.get<User>('/users/1'));Framework Integration
React Hook
tsx
import { createHttpClient, createQueryClient } from '@vielzeug/fetchit';
import { useEffect, useState } from 'react';
const http = createHttpClient({
baseUrl: 'https://api.example.com',
});
const queryClient = createQueryClient();
function useUser(userId: string) {
const [data, setData] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
const fetchUser = async () => {
try {
setLoading(true);
const user = await queryClient.fetch({
queryKey: ['users', userId],
queryFn: () => http.get<User>(`/users/${userId}`),
});
if (!cancelled) {
setData(user);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err instanceof Error ? err : new Error('Failed'));
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
fetchUser();
return () => {
cancelled = true;
};
}, [userId]);
return { data, loading, error };
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, loading, error } = useUser(userId);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
}Vue Composable
ts
import { createHttpClient } from '@vielzeug/fetchit';
import { ref, watchEffect } from 'vue';
const http = createHttpClient({
baseUrl: 'https://api.example.com',
});
export function useUser(userId: Ref<string>) {
const data = ref<User | null>(null);
const loading = ref(true);
const error = ref<Error | null>(null);
watchEffect(async () => {
try {
loading.value = true;
const user = await http.get<User>(`/users/${userId.value}`);
data.value = user;
error.value = null;
} catch (err) {
error.value = err instanceof Error ? err : new Error('Failed');
} finally {
loading.value = false;
}
});
return { data, loading, error };
}SvelteKit
ts
import { createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({
baseUrl: 'https://api.example.com',
});
// +page.server.ts
export async function load({ params }) {
const user = await http.get<User>(`/users/${params.id}`);
return {
user,
};
}Advanced Patterns
Retry Logic
Fetchit uses @vielzeug/toolkit's retry() utility for intelligent retry logic with exponential backoff:
ts
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const queryClient = createQueryClient();
// Query with automatic retry
const user = await queryClient.fetch({
queryKey: ['users', userId],
queryFn: () => http.get<User>(`/users/${userId}`),
retry: 3, // Retry 3 times with exponential backoff (1s, 2s, 4s)
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
});
// Mutation with retry
await queryClient.mutate(
{
mutationFn: (data) => http.post<User>('/users', { body: data }),
retry: 2, // Retry POST operations 2 times
},
{ name: 'Alice' },
);
// Custom fixed retry delay
const data = await queryClient.fetch({
queryKey: ['status'],
queryFn: () => http.get('/status'),
retry: 5,
retryDelay: 2000, // Fixed 2s delay between retries
});Polling
ts
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const queryClient = createQueryClient();
function startPolling(interval: number, onData: (data: any) => void) {
const pollId = setInterval(async () => {
try {
// Invalidate to force refetch
queryClient.invalidate(['status']);
const data = await queryClient.fetch({
queryKey: ['status'],
queryFn: () => http.get('/status'),
});
onData(data);
} catch (error) {
console.error('Polling error:', error);
}
}, interval);
return () => clearInterval(pollId);
}
// Usage
const stopPolling = startPolling('/status', 5000, (status) => {
console.log('Status update:', status);
});
// Later: stopPolling();Batch Requests
ts
import { createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
async function batchFetch<T>(ids: string[]): Promise<T[]> {
const requests = ids.map((id) => http.get<T>(`/users/${id}`));
return await Promise.all(requests);
}
// Usage
const users = await batchFetch<User>(['1', '2', '3']);