Fetchit Usage Guide
Complete guide to installing and using Fetchit in your projects.
Installation
sh
pnpm add @vielzeug/fetchitsh
npm install @vielzeug/fetchitsh
yarn add @vielzeug/fetchitImport
ts
import { createHttpClient, createQueryClient, HttpError } from '@vielzeug/fetchit';💡 API Reference
This guide covers API usage and basic patterns. For complete application examples, see Examples.
Two Ways to Use Fetchit
Fetchit provides flexible architecture with separate clients:
- HTTP Client - Pure HTTP operations without query overhead
- Query Client - Advanced caching and state management
- Use Together or Independently - Mix and match based on your needs
HTTP Client (Simple HTTP Requests)
The HTTP client provides clean REST API methods without caching overhead. Perfect for simple requests.
Creating an HTTP Client
ts
import { createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({
baseUrl: 'https://api.example.com',
timeout: 30000, // 30 seconds (default: 30000)
headers: {
Authorization: 'Bearer token',
'Content-Type': 'application/json',
},
dedupe: true, // Automatic request deduplication (default: true)
logger: (level, msg, meta) => {
// Optional: Custom logger for debugging
console.log(`[${level.toUpperCase()}]`, msg, meta);
},
});Options:
baseUrl- Base URL for all requeststimeout- Request timeout in milliseconds (default: 30000)headers- Default headers for all requestsdedupe- Enable request deduplication (default: true)logger- Optional logger function for debugging requests
Making Requests
ts
// GET request - returns raw data
const user = await http.get<User>('/users/1');
console.log(user.name);
// POST request
const created = await http.post<User>('/users', {
body: {
name: 'Alice',
email: 'alice@example.com',
},
});
// PUT request
const updated = await http.put<User>('/users/1', {
body: { name: 'Alice Smith' },
});
// PATCH request
const patched = await http.patch<User>('/users/1', {
body: { email: 'newemail@example.com' },
});
// DELETE request
await http.delete('/users/1');
// Custom method
const data = await http.request<DataType>('CUSTOM', '/endpoint');Query Parameters
ts
const users = await http.get<User[]>('/users', {
params: {
role: 'admin',
age: 18,
page: 1,
},
});
// Calls: /users?role=admin&age=18&page=1Managing Headers
ts
// Update headers dynamically
http.setHeaders({
Authorization: `Bearer ${newToken}`,
});
// Remove a header
http.setHeaders({
Authorization: undefined,
});
// Get current headers
const headers = http.getHeaders();
console.log(headers);Query Client (Advanced Caching)
The Query client provides intelligent caching, request deduplication, and state management. Works with any HTTP client or fetch function.
Creating a Query Client
ts
import { createQueryClient, createHttpClient } from '@vielzeug/fetchit';
const queryClient = createQueryClient({
cache: {
staleTime: 5000, // Data fresh for 5 seconds
gcTime: 300000, // Keep in cache for 5 minutes
enabled: true,
},
refetch: {
onFocus: true, // Refetch when window regains focus
onReconnect: true, // Refetch when network reconnects
},
});
// Use with HTTP client
const http = createHttpClient({ baseUrl: 'https://api.example.com' });Type-Safe Query Keys
ts
// Define query keys manually with `as const` for type safety
const queryKeys = {
users: {
all: () => ['users'] as const,
detail: (id: string) => ['users', id] as const,
list: (filters: { role?: string }) => ['users', 'list', filters] as const,
},
} as const;
// Type-safe and autocomplete works!
const user = await queryClient.fetch({
queryKey: queryKeys.users.detail('123'),
queryFn: () => http.get(`/users/123`),
});Basic Query
ts
// Fetch user with caching
const user = await queryClient.fetch({
queryKey: ['users', userId],
queryFn: () => http.get<User>(`/users/${userId}`),
staleTime: 5000, // Fresh for 5 seconds
gcTime: 60000, // Keep in cache for 60 seconds
});Query with Parameters
ts
// Query key includes all parameters that affect the data
async function fetchUsers(filters: { role?: string; age?: number }) {
return queryClient.fetch({
queryKey: ['users', filters], // Include filters in key
queryFn: () => http.get<User[]>('/users', { params: filters }),
});
}
const admins = await fetchUsers({ role: 'admin' });
const adults = await fetchUsers({ age: 18 });Mutations (POST/PUT/DELETE)
ts
// Create user
const newUser = await queryClient.mutate(
{
mutationFn: (userData: CreateUserInput) => http.post<User>('/users', { body: userData }),
onSuccess: (data) => {
// Invalidate users list to refetch
queryClient.invalidate(['users']);
},
},
{
name: 'John Doe',
email: 'john@example.com',
},
);Optimistic Updates
ts
// Update user optimistically
queryClient.setData<User>(['users', userId], (old) => ({
...old,
name: 'Updated Name',
}));
try {
await queryClient.mutate(
{
mutationFn: (updates) => http.put<User>(`/users/${userId}`, { body: updates }),
onSuccess: () => {
// Refetch to get server data
queryClient.invalidate(['users', userId]);
},
},
{ name: 'Updated Name' },
);
} catch (error) {
// Rollback on error
queryClient.invalidate(['users', userId]);
}Cache Management
ts
// Invalidate specific query
queryClient.invalidate(['users', userId]);
// Manually set cache data
queryClient.setData(['users', 1], { id: 1, name: 'John' });
// Get cached data
const cachedUser = queryClient.getData(['users', 1]);
// Get query state (includes status, error, etc.)
const state = queryClient.getState(['users', 1]);
console.log(state?.status, state?.data);
// Subscribe to query changes
const unsubscribe = queryClient.subscribe(['users', userId], (state) => {
console.log('User data changed:', state.data);
console.log('Loading:', state.isLoading);
});
// Prefetch data
await queryClient.prefetch({
queryKey: ['users', '2'],
queryFn: () => http.get('/users/2'),
});
// Clear all cache
queryClient.clearCache();
// Get cache size
const size = queryClient.getCacheSize();Observable State
Subscribe to query state changes for real-time updates:
ts
const unsubscribe = queryClient.subscribe(['users', userId], (state) => {
console.log('Status:', state.status); // 'idle' | 'pending' | 'success' | 'error'
console.log('Data:', state.data);
console.log('Error:', state.error);
console.log('Loading:', state.isLoading);
console.log('Success:', state.isSuccess);
console.log('HTTP Status:', state.httpStatus);
console.log('HTTP OK:', state.httpOk);
});
// Don't forget to cleanup
unsubscribe();Common Patterns
Query with Retry
ts
import { createQueryClient, createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const queryClient = createQueryClient();
const user = await queryClient.fetch({
queryKey: ['users', userId],
queryFn: () => http.get<User>(`/users/${userId}`),
retry: 3, // Retry 3 times on failure
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
});Dependent Queries
ts
// Fetch user first
const user = await queryClient.fetch({
queryKey: ['users', userId],
queryFn: () => http.get<User>(`/users/${userId}`),
});
// Then fetch their posts
const posts = await queryClient.fetch({
queryKey: ['users', userId, 'posts'],
queryFn: () => http.get<Post[]>(`/users/${userId}/posts`),
enabled: !!user, // Only run if user exists
});Mutation with Invalidation
ts
await queryClient.mutate(
{
mutationFn: (postData) => http.post<Post>('/posts', { body: postData }),
onSuccess: () => {
// Invalidate posts list to refetch
queryClient.invalidate(['posts']);
// Also invalidate user's posts
queryClient.invalidate(['users', userId, 'posts']);
},
},
postData,
);Advanced Features
Dynamic Headers
Update headers after client creation:
ts
import { createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
// Add or update headers
http.setHeaders({
Authorization: 'Bearer new-token',
'X-Custom-Header': 'value',
});
// Remove headers (set to undefined)
http.setHeaders({
Authorization: undefined,
});
// Get current headers
const headers = http.getHeaders();Query Options
All available query options:
ts
import { createQueryClient, createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const queryClient = createQueryClient();
await queryClient.fetch({
queryKey: ['users', userId],
queryFn: () => http.get<User>(`/users/${userId}`),
// Caching
staleTime: 5000, // Data fresh for 5 seconds
gcTime: 60000, // Keep in cache for 60 seconds
// Execution
enabled: true, // Enable/disable query
// Retry
retry: 3, // Number of retries (or false)
retryDelay: 1000, // Delay between retries (or function)
// Refetching
refetchOnFocus: false, // Refetch when window gains focus
refetchOnReconnect: false, // Refetch when going online
// Callbacks
onSuccess: (data) => {
console.log('Success:', data);
},
onError: (error) => {
console.error('Error:', error);
},
});Mutation Options
All available mutation options:
ts
import { createQueryClient, createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const queryClient = createQueryClient();
await queryClient.mutate(
{
mutationFn: (userData) => http.post<User>('/users', { body: userData }),
// Retry
retry: false, // Don't retry mutations by default
// Callbacks
onSuccess: (data, variables) => {
console.log('Created:', data);
},
onError: (error, variables) => {
console.error('Failed:', error);
},
onSettled: (data, error, variables) => {
// Always called (success or error)
},
},
userData,
);Cache Management
ts
import { createQueryClient } from '@vielzeug/fetchit';
const queryClient = createQueryClient();
// Invalidate specific query
queryClient.invalidate(['users', 1]);
// Set query data manually (optimistic updates)
queryClient.setData(['users', 1], { id: 1, name: 'John' });
// Update with function
queryClient.setData<User[]>(['users'], (old = []) => [...old, newUser]);
// Get cached data
const cachedUser = queryClient.getData(['users', 1]);
// Clear all cache
queryClient.clearCache();
// Get cache size
const size = queryClient.getCacheSize();URL Building
ts
import { buildUrl } from '@vielzeug/fetchit';
const url = buildUrl('/api/users', {
page: 1,
limit: 10,
active: true,
});
// Result: "/api/users?page=1&limit=10&active=true"Error Handling
ts
import { HttpError } from '@vielzeug/fetchit';
try {
await api.get('/users/1');
} catch (error) {
if (error instanceof HttpError) {
console.error(`Request failed: ${error.method} ${error.url}`);
console.error(`Status: ${error.status}`);
console.error(`Message: ${error.message}`);
}
}File Uploads
Fetchit automatically handles FormData:
ts
import { createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('description', 'My file');
// Content-Type is set automatically by the browser
await http.post('/upload', { body: formData });Binary Data
ts
import { createHttpClient } from '@vielzeug/fetchit';
const http = createHttpClient({ baseUrl: 'https://api.example.com' });
// Blob
const blob = new Blob(['content'], { type: 'text/plain' });
await http.post('/upload', { body: blob });
// ArrayBuffer
const buffer = new ArrayBuffer(8);
await http.post('/data', { body: buffer });
// URLSearchParams
const params = new URLSearchParams();
params.append('key', 'value');
await http.post('/form', { body: params });Configuration Options
ContextProps
ts
{
url: string; // Base URL (required)
timeout?: number; // Request timeout in ms (default: 5000)
headers?: Record<string, string | undefined>; // Default headers
expiresIn?: number; // Cache expiration in ms (default: 120000)
params?: Record<string, string | number | undefined>; // Default query params
}RequestConfig
ts
{
id?: string; // Custom cache key
cancelable?: boolean; // Cancel pending requests with same ID
invalidate?: boolean; // Force bypass cache
body?: unknown; // Request body
headers?: HeadersInit; // Per-request headers
// ...all standard RequestInit options
}Best Practices
- Create one client per API: Don't create a new client for each request
- Use custom IDs for cache control: Makes invalidation easier
- Handle errors properly: Use HttpError for better debugging
- Clean up on logout: Call
clearCache()when user logs out - Use TypeScript: Define response types for better type safety
See Also
- API Reference - Complete API documentation
- Examples - Practical code examples