Setup and Configuration
The Aurelia Fetch Client provides multiple ways to create and configure HTTP clients. You can create instances directly, use dependency injection, or configure shared instances for your application.
Quick Start
The simplest way to get started is by resolving the fetch client through Aurelia's dependency injection:
import { IHttpClient } from '@aurelia/fetch-client';
import { resolve } from '@aurelia/kernel';
export class ApiService {
private http = resolve(IHttpClient);
async getUsers() {
const response = await this.http.get('/api/users');
return response.json();
}
}
Creating Instances
Using Dependency Injection (Recommended)
import { IHttpClient } from '@aurelia/fetch-client';
import { resolve } from '@aurelia/kernel';
export class UserService {
private http = resolve(IHttpClient);
constructor() {
// Configure this instance
this.http.configure(config => config
.withBaseUrl('https://api.example.com/')
.withDefaults({
headers: { 'Authorization': 'Bearer token' }
})
);
}
}
Creating New Instances
import { HttpClient } from '@aurelia/fetch-client';
const httpClient = new HttpClient();
httpClient.configure(config => config
.withDefaults({ mode: 'cors' })
.withBaseUrl('https://api.example.com/')
);
const users = await httpClient.get('users')
.then(response => response.json());
Multiple Configured Instances
For applications that need to communicate with different APIs:
import { IHttpClient } from '@aurelia/fetch-client';
import { resolve, newInstanceOf } from '@aurelia/kernel';
export class MultiApiService {
// Separate instances for different APIs
private mainApi = resolve(newInstanceOf(IHttpClient));
private authApi = resolve(newInstanceOf(IHttpClient));
constructor() {
this.mainApi.configure(config => config
.withBaseUrl('https://api.example.com/v1/')
.withDefaults({
headers: { 'Content-Type': 'application/json' }
})
);
this.authApi.configure(config => config
.withBaseUrl('https://auth.example.com/')
.withDefaults({
headers: { 'X-Client-ID': 'your-client-id' }
})
);
}
}
Best Practice: Avoid creating multiple instances unnecessarily. Instead, create service classes that encapsulate your HTTP logic with properly configured clients.
Configuration Options
The Aurelia Fetch Client supports all native Fetch API options plus additional convenience methods for common scenarios.
Basic Configuration
import { IHttpClient } from '@aurelia/fetch-client';
import { resolve } from '@aurelia/kernel';
export class ApiService {
private http = resolve(IHttpClient);
constructor() {
this.http.configure(config => config
.withBaseUrl('https://api.example.com/v1/')
.withDefaults({
credentials: 'same-origin',
mode: 'cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'Fetch'
}
})
.withInterceptor({
request(request) {
console.log(`→ ${request.method} ${request.url}`);
return request;
},
response(response) {
console.log(`← ${response.status} ${response.url}`);
return response;
}
})
);
}
}
Configuration Methods
withBaseUrl(url: string)
withBaseUrl(url: string)
Sets the base URL for all relative requests:
config.withBaseUrl('https://api.example.com/v1/');
// Later requests:
http.get('/users'); // → https://api.example.com/v1/users
http.get('posts'); // → https://api.example.com/v1/posts
withDefaults(options: RequestInit)
withDefaults(options: RequestInit)
Sets default options merged with every request:
config.withDefaults({
credentials: 'include',
mode: 'cors',
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json'
}
});
withInterceptor(interceptor: IFetchInterceptor)
withInterceptor(interceptor: IFetchInterceptor)
Adds request/response interceptors for cross-cutting concerns:
config.withInterceptor({
request(request) {
// Modify outgoing requests
request.headers.set('X-Timestamp', Date.now().toString());
return request;
},
response(response) {
// Process incoming responses
if (!response.ok) {
console.warn(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
}
});
useStandardConfiguration()
useStandardConfiguration()
Applies common defaults for typical applications:
config.useStandardConfiguration();
// Equivalent to:
config
.withDefaults({ credentials: 'same-origin' })
.rejectErrorResponses();
rejectErrorResponses()
rejectErrorResponses()
Makes the client reject promises for HTTP error status codes (4xx, 5xx):
config.rejectErrorResponses();
// Now 4xx/5xx responses will reject the promise
try {
await http.get('/api/invalid-endpoint');
} catch (error) {
console.log('HTTP error:', error.status); // e.g., 404
}
withRetry(options: IRetryConfiguration)
withRetry(options: IRetryConfiguration)
Enables automatic retries for failed requests:
import { RetryStrategy } from '@aurelia/fetch-client';
config.withRetry({
maxRetries: 3,
strategy: RetryStrategy.exponential,
doRetry: (response) => response.status >= 500
});
Advanced Configuration
Dynamic Headers
Headers can be functions that are evaluated for each request:
config.withDefaults({
headers: {
'Authorization': () => `Bearer ${getCurrentToken()}`,
'X-Request-ID': () => generateUUID(),
'X-Timestamp': () => new Date().toISOString()
}
});
Request Event Dispatcher
Enable events for request lifecycle monitoring:
config.withDispatcher(document.body);
// Listen for events
document.body.addEventListener('aurelia-fetch-client-request-started', (e) => {
console.log('Request started');
});
document.body.addEventListener('aurelia-fetch-client-requests-drained', (e) => {
console.log('All requests completed');
});
Making Requests
HTTP Methods
The fetch client provides convenient methods for all standard HTTP verbs:
// GET request
const users = await http.get('/api/users');
const userData = await users.json();
// POST with JSON body
const newUser = await http.post('/api/users', {
body: json({ name: 'John', email: '[email protected]' })
});
// PUT request
await http.put(`/api/users/${userId}`, {
body: json(updatedUser)
});
// PATCH request
await http.patch(`/api/users/${userId}`, {
body: json({ status: 'active' })
});
// DELETE request
await http.delete(`/api/users/${userId}`);
JSON Helper
The json()
helper automatically stringifies objects and sets the correct content-type:
import { IHttpClient, json } from '@aurelia/fetch-client';
export class CommentService {
private http = resolve(IHttpClient);
async createComment(commentData) {
return this.http.post('/api/comments', {
body: json(commentData) // Automatically sets Content-Type: application/json
});
}
// Equivalent to the manual approach:
async createCommentManual(commentData) {
return this.http.post('/api/comments', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(commentData)
});
}
}
Request Options
All methods accept standard Fetch API options:
// GET with custom headers
const response = await http.get('/api/data', {
headers: {
'Accept': 'application/xml',
'X-Custom-Header': 'value'
}
});
// POST with credentials
await http.post('/api/secure-endpoint', {
credentials: 'include',
body: json(data)
});
// Request with AbortController
const controller = new AbortController();
const promise = http.get('/api/slow-endpoint', {
signal: controller.signal
});
// Cancel the request after 5 seconds
setTimeout(() => controller.abort(), 5000);
Response Handling
const response = await http.get('/api/users');
// Check response status
if (response.ok) {
const users = await response.json();
console.log(users);
} else {
console.error(`HTTP ${response.status}: ${response.statusText}`);
}
// Or use rejectErrorResponses() to handle this automatically
http.configure(config => config.rejectErrorResponses());
try {
const response = await http.get('/api/users');
const users = await response.json();
} catch (error) {
if (error instanceof Response) {
console.error(`HTTP ${error.status}: ${error.statusText}`);
}
}
Error Handling Strategies
Basic Error Handling
The native Fetch API only rejects promises for network errors, not HTTP error status codes. Use rejectErrorResponses()
to change this behavior:
http.configure(config => config.rejectErrorResponses());
try {
const response = await http.get('/api/users');
const users = await response.json();
} catch (error) {
if (error instanceof Response) {
// HTTP error (4xx, 5xx)
console.error(`HTTP ${error.status}: ${error.statusText}`);
if (error.status === 401) {
// Handle unauthorized
redirectToLogin();
} else if (error.status >= 500) {
// Handle server errors
showServerErrorMessage();
}
} else {
// Network error
console.error('Network error:', error);
showNetworkErrorMessage();
}
}
Centralized Error Handling
Use interceptors to handle errors globally:
http.configure(config => config.withInterceptor({
response(response) {
if (!response.ok) {
logError(`HTTP ${response.status}`, response.url);
// Still return the response to let calling code handle it
return response;
}
return response;
},
responseError(error, request) {
console.error('Request failed:', {
url: request?.url,
method: request?.method,
error: error.message
});
// Show user-friendly error message
showNotification('Something went wrong. Please try again.');
// Re-throw to let calling code handle if needed
throw error;
}
}));
function logError(message, url) {
// Send to logging service
logger.error(message, { url, timestamp: new Date() });
}
Recovery Patterns
Implement automatic recovery for common scenarios:
http.configure(config => config.withInterceptor({
async responseError(error, request, client) {
if (error instanceof Response && error.status === 401) {
// Try to refresh auth token
try {
await refreshAuthToken();
// Retry the original request with new token
const newRequest = new Request(request.url, {
method: request.method,
headers: {
...Object.fromEntries(request.headers.entries()),
'Authorization': `Bearer ${getNewToken()}`
},
body: request.body
});
return client.fetch(newRequest);
} catch (refreshError) {
// Refresh failed, redirect to login
redirectToLogin();
throw error;
}
}
throw error;
}
}));
Automatic Retries
The fetch client includes built-in retry functionality for handling transient network failures:
Basic Retry Configuration
import { RetryStrategy } from '@aurelia/fetch-client';
http.configure(config => config.withRetry({
maxRetries: 3,
interval: 1000,
strategy: RetryStrategy.exponential
}));
Retry Strategies
Fixed Interval
Retries with the same delay between attempts:
config.withRetry({
maxRetries: 3,
interval: 2000,
strategy: RetryStrategy.fixed
});
// Retries after: 2s, 2s, 2s
Incremental Backoff
Increases delay with each retry:
config.withRetry({
maxRetries: 3,
interval: 1000,
strategy: RetryStrategy.incremental
});
// Retries after: 1s, 2s, 3s
Exponential Backoff
Doubles the delay with each retry:
config.withRetry({
maxRetries: 3,
interval: 1000,
strategy: RetryStrategy.exponential
});
// Retries after: 1s, 2s, 4s
Random Interval
Random delay within specified bounds:
config.withRetry({
maxRetries: 3,
strategy: RetryStrategy.random,
minRandomInterval: 500,
maxRandomInterval: 2000
});
// Retries after random intervals between 500ms-2000ms
Custom Strategy
Provide your own retry timing logic:
config.withRetry({
maxRetries: 5,
strategy: (retryCount) => {
// Custom backoff: 100ms, 200ms, 400ms, 800ms, 1600ms
return Math.min(100 * Math.pow(2, retryCount), 5000);
}
});
Conditional Retries
Control which requests should be retried:
config.withRetry({
maxRetries: 3,
strategy: RetryStrategy.exponential,
// Only retry server errors, not client errors
doRetry: (response, request) => {
return response.status >= 500;
},
// Modify request before retry
beforeRetry: (request, client) => {
// Add a retry header
request.headers.set('X-Retry-Count', retryCount.toString());
return request;
}
});
Complete Retry Configuration
interface IRetryConfiguration {
maxRetries: number; // Maximum retry attempts
interval?: number; // Base interval in milliseconds
strategy?: RetryStrategy | ((retryCount: number) => number); // Retry timing strategy
minRandomInterval?: number; // Min random interval (for random strategy)
maxRandomInterval?: number; // Max random interval (for random strategy)
doRetry?(response: Response, request: Request): boolean | Promise<boolean>; // Conditional retry logic
beforeRetry?(request: Request, client: HttpClient): Request | Promise<Request>; // Request modification before retry
}
Real-world Example
http.configure(config => config.withRetry({
maxRetries: 3,
strategy: RetryStrategy.exponential,
interval: 1000,
// Only retry on server errors or network failures
doRetry: (response, request) => {
// Retry on 5xx server errors
if (response.status >= 500) return true;
// Don't retry on client errors (4xx)
if (response.status >= 400 && response.status < 500) return false;
// Retry on network errors (no response)
return !response;
},
beforeRetry: async (request, client) => {
// Refresh auth token before retry if needed
if (request.headers.get('Authorization')) {
const newToken = await refreshTokenIfNeeded();
request.headers.set('Authorization', `Bearer ${newToken}`);
}
// Add retry tracking
request.headers.set('X-Retry-Attempt', Date.now().toString());
return request;
}
}));
Important: Only one retry interceptor can be configured per client, and it must be the last interceptor in the chain.
Last updated
Was this helpful?