This guide covers configuration options and patterns.
// Set base URL for all requests
const api = {
get: (path: string, options = {}) =>
fetch.get(path, { ...options, baseURL: 'https://api.example.com' }),
post: (path: string, body?: any, options = {}) =>
fetch.post(path, body, { ...options, baseURL: 'https://api.example.com' })
}
// Usage
const users = await api.get('/users')
const newUser = await api.post('/users', { name: 'John' })// Different base URLs for different services
const services = {
auth: (path: string, options = {}) =>
fetch.get(path, { ...options, baseURL: 'https://auth.example.com' }),
api: (path: string, options = {}) =>
fetch.get(path, { ...options, baseURL: 'https://api.example.com' }),
storage: (path: string, options = {}) =>
fetch.get(path, { ...options, baseURL: 'https://storage.example.com' })
}
// Usage
const token = await services.auth('/token')
const data = await services.api('/data')
const file = await services.storage('/files/image.jpg')// BaseURL based on environment
function getBaseURL() {
if (process.env.NODE_ENV === 'production') {
return 'https://api.example.com'
} else if (process.env.NODE_ENV === 'staging') {
return 'https://staging-api.example.com'
} else {
return 'http://localhost:3000'
}
}
const response = await fetch.get('/users', {
baseURL: getBaseURL()
})// Common headers for all requests
const defaultHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'MyApp/1.0.0'
}
const response = await fetch.get('https://api.example.com/data', {
headers: defaultHeaders
})// Headers that change based on request
async function fetchWithAuth(url: string, token: string) {
return await fetch.get(url, {
headers: {
'Authorization': `Bearer ${token}`,
'X-Request-ID': Math.random().toString(36).substr(2, 9),
'X-Timestamp': Date.now().toString()
}
})
}// Base headers with request-specific additions
const baseHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
const response = await fetch.post('https://api.example.com/data', data, {
headers: {
...baseHeaders,
'Authorization': 'Bearer token123',
'X-Custom-Header': 'custom-value'
}
})// Simple timeout
const response = await fetch.get('https://api.example.com/data', {
timeout: 5000 // 5 seconds
})// Different timeouts for different operations
const quickData = await fetch.get('/api/quick', { timeout: 2000 })
const slowData = await fetch.get('/api/slow', { timeout: 30000 })
const uploadData = await fetch.post('/api/upload', data, { timeout: 60000 })async function fetchWithFallback(url: string, fallbackUrl: string) {
const response = await fetch.get(url, { timeout: 3000 })
return response
}// Simple retry configuration
const response = await fetch.get('https://api.example.com/data', {
retries: 3
})// Custom retry logic based on error type
async function fetchWithConditionalRetry(url: string) {
const response = await fetch.get(url, { retries: 5 })
return response
}// Combine retries with custom backoff
async function fetchWithBackoff(url: string) {
const response = await fetch.get(url, {
timeout: 5000,
retries: 3
})
return response
}// Simple request cancellation
const controller = new AbortController()
const promise = fetch.get('https://api.example.com/slow-data', {
signal: controller.signal
})
// Cancel after 2 seconds
setTimeout(() => controller.abort(), 2000)
const data = await promise// Manual timeout using AbortSignal
function fetchWithManualTimeout(url: string, timeoutMs: number) {
const controller = new AbortController()
const timeoutId = setTimeout(() => {
controller.abort()
}, timeoutMs)
return fetch.get(url, { signal: controller.signal })
.finally(() => {
clearTimeout(timeoutId)
})
}
// Usage
const data = await fetchWithManualTimeout('https://api.example.com/data', 5000)// Cancel multiple requests with one signal
const controller = new AbortController()
const signal = controller.signal
const promises = [
fetch.get('/api/data1', { signal }),
fetch.get('/api/data2', { signal }),
fetch.get('/api/data3', { signal })
]
// Cancel all requests
setTimeout(() => controller.abort(), 3000)
const results = await Promise.all(promises)// Let the library detect response type (default)
const jsonData = await fetch.get('https://api.example.com/json-data')
const textData = await fetch.get('https://api.example.com/text-data')
const binaryData = await fetch.get('https://api.example.com/binary-data')// Force specific response types
const jsonResponse = await fetch.get('https://api.example.com/data', {
responseType: 'json'
})
const textResponse = await fetch.get('https://api.example.com/text', {
responseType: 'text'
})
const bufferResponse = await fetch.get('https://api.example.com/file', {
responseType: 'buffer'
})
const blobResponse = await fetch.get('https://api.example.com/image', {
responseType: 'blob'
})// Different response types for different content
async function fetchContent(url: string, contentType: string) {
let responseType: 'json' | 'text' | 'buffer' | 'blob' = 'auto'
if (contentType.includes('json')) {
responseType = 'json'
} else if (contentType.includes('text')) {
responseType = 'text'
} else if (contentType.includes('image') || contentType.includes('pdf')) {
responseType = 'blob'
} else {
responseType = 'buffer'
}
return await fetch.get(url, { responseType })
}// All options combined
const response = await fetch.post('https://api.example.com/complex-endpoint', {
data: { key: 'value' }
}, {
baseURL: 'https://api.example.com',
timeout: 30000,
retries: 3,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123',
'X-API-Version': 'v2'
},
signal: controller.signal,
responseType: 'json'
})// Create reusable configuration
function createApiConfig(baseURL: string, token: string) {
return {
baseURL,
timeout: 10000,
retries: 2,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}
}
// Usage
const config = createApiConfig('https://api.example.com', 'token123')
const data = await fetch.get('/users', config)// Create a wrapper with automatic token refresh
class AuthenticatedFetch {
private accessToken: string
private refreshToken: string
constructor(accessToken: string, refreshToken: string) {
this.accessToken = accessToken
this.refreshToken = refreshToken
}
private async refreshAccessToken(): Promise<string> {
const response = await fetch.post('https://auth.example.com/refresh', {
refreshToken: this.refreshToken
})
this.accessToken = response.accessToken
return this.accessToken
}
async request(url: string, options: any = {}) {
const headers = {
'Authorization': `Bearer ${this.accessToken}`,
...options.headers
}
return await fetch.request(url, { ...options, headers })
}
}// Retry with custom conditions
async function fetchWithCustomRetry(url: string, options: any = {}) {
const response = await fetch.get(url, {
...options,
retries: options.retries || 3
})
return response
}// Circuit breaker for fault tolerance
class CircuitBreaker {
private failures = 0
private lastFailureTime = 0
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'
private threshold: number
private timeout: number
constructor(threshold = 5, timeout = 60000) {
this.threshold = threshold
this.timeout = timeout
}
async execute(url: string, options: any) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = 'HALF_OPEN'
}
}
const result = await fetch.get(url, options)
this.onSuccess()
return result
}
private onSuccess() {
this.failures = 0
this.state = 'CLOSED'
}
private onFailure() {
this.failures++
this.lastFailureTime = Date.now()
if (this.failures >= this.threshold) {
this.state = 'OPEN'
}
}
}// Fetch multiple resources simultaneously
async function fetchMultipleUsers(userIds: number[]) {
const promises = userIds.map(id =>
fetch.get(`https://jsonplaceholder.typicode.com/users/${id}`)
)
const users = await Promise.all(promises)
return users
}// Fetch data in sequence with dependencies
async function fetchUserWithPosts(userId: number) {
// First get user
const user = await fetch.get(`https://jsonplaceholder.typicode.com/users/${userId}`)
// Then get user's posts
const posts = await fetch.get(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
// Finally get comments for each post
const postsWithComments = await Promise.all(
posts.map(async (post: any) => {
const comments = await fetch.get(`https://jsonplaceholder.typicode.com/posts/${post.id}/comments`)
return { ...post, comments }
})
)
return { user, posts: postsWithComments }
}// Simple in-memory cache
class CachedFetch {
private cache = new Map()
private ttl: number
constructor(ttl = 300000) { // 5 minutes
this.ttl = ttl
}
async get(url: string, options: any = {}) {
const cacheKey = `${url}-${JSON.stringify(options)}`
const cached = this.cache.get(cacheKey)
if (cached && Date.now() - cached.timestamp < this.ttl) {
console.log('Cache hit for:', url)
return cached.data
}
console.log('Cache miss, fetching:', url)
const data = await fetch.get(url, options)
this.cache.set(cacheKey, {
data,
timestamp: Date.now()
})
return data
}
clearCache() {
this.cache.clear()
}
}// Prevent duplicate requests
class DeduplicatedFetch {
private pendingRequests = new Map()
async get(url: string, options: any = {}) {
const requestKey = `${url}-${JSON.stringify(options)}`
if (this.pendingRequests.has(requestKey)) {
console.log('Deduplicating request:', url)
return this.pendingRequests.get(requestKey)
}
const promise = fetch.get(url, options)
.finally(() => {
this.pendingRequests.delete(requestKey)
})
this.pendingRequests.set(requestKey, promise)
return promise
}
}// HMAC request signing
class SignedFetch {
private apiKey: string
private secretKey: string
constructor(apiKey: string, secretKey: string) {
this.apiKey = apiKey
this.secretKey = secretKey
}
private signRequest(method: string, url: string, body: string = '') {
const timestamp = Date.now().toString()
const nonce = crypto.randomBytes(16).toString('hex')
const message = `${method}${url}${body}${timestamp}${nonce}`
const signature = crypto
.createHmac('sha256', this.secretKey)
.update(message)
.digest('hex')
return {
'X-API-Key': this.apiKey,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature
}
}
async request(method: string, url: string, body?: any) {
const bodyString = body ? JSON.stringify(body) : ''
const headers = this.signRequest(method, url, bodyString)
return await fetch.request(url, {
method,
body: bodyString,
headers: {
'Content-Type': 'application/json',
...headers
}
})
}
}// Request logging
class LoggedFetch {
private logger: any
constructor(logger: any) {
this.logger = logger
}
async request(url: string, options: any = {}) {
const startTime = Date.now()
const requestId = Math.random().toString(36).substr(2, 9)
this.logger.info('Request started', {
requestId,
url,
method: options.method || 'GET',
headers: options.headers
})
const response = await fetch.request(url, options)
const duration = Date.now() - startTime
this.logger.info('Request completed', {
requestId,
url,
status: response.status,
duration: `${duration}ms`
})
return response
}
}// Simple API calls
async function simpleApiCall(url: string, options: any = {}) {
const response = await fetch.get(url, options)
return response
}// Strongly typed API client
interface User {
id: number
name: string
email: string
}
interface ApiResponse<T> {
data: T
status: number
message: string
}
class TypedApiClient {
private baseURL: string
constructor(baseURL: string) {
this.baseURL = baseURL
}
async getUsers(): Promise<ApiResponse<User[]>> {
return await fetch.get('/users', { baseURL: this.baseURL })
}
async getUser(id: number): Promise<ApiResponse<User>> {
return await fetch.get(`/users/${id}`, { baseURL: this.baseURL })
}
async createUser(user: Omit<User, 'id'>): Promise<ApiResponse<User>> {
return await fetch.post('/users', user, { baseURL: this.baseURL })
}
}- Error Handling - Error handling patterns