Advanced Usage
Advanced features and patterns for the HTTP client plugin.
Interceptors
Interceptors allow you to intercept and modify requests and responses.
Request Interceptors
Add custom logic before requests are sent:
import { createAuthInterceptor, createCorrelationIdInterceptor } from '@stratix/http-client';
// Dynamic authentication
const authInterceptor = createAuthInterceptor(
async () => await getAuthToken(),
'Authorization',
'Bearer'
);
client.addRequestInterceptor(authInterceptor);
// Correlation IDs
const correlationInterceptor = createCorrelationIdInterceptor('X-Correlation-ID');
client.addRequestInterceptor(correlationInterceptor);
// Custom interceptor
client.addRequestInterceptor({
onFulfilled: (config) => {
config.headers['X-Request-Time'] = new Date().toISOString();
return config;
},
onRejected: (error) => {
console.error('Request configuration error:', error);
return Promise.reject(error);
}
});
Response Interceptors
Transform responses or handle errors globally:
// Transform response data
client.addResponseInterceptor({
onFulfilled: (response) => {
// Convert snake_case to camelCase
return {
...response,
data: camelCaseKeys(response.data)
};
}
});
// Global error handling
client.addResponseInterceptor({
onRejected: async (error) => {
// Refresh token on 401
if (error.statusCode === 401) {
await refreshAuthToken();
return client.request(error.config);
}
// Log errors
logger.error('HTTP Error', {
url: error.config?.url,
status: error.statusCode,
message: error.message
});
return Promise.reject(error);
}
});
Built-in Interceptors
import {
createLoggingInterceptor,
createTimingInterceptor,
createRetryInterceptor,
createCorrelationIdInterceptor,
createAuthInterceptor
} from '@stratix/http-client';
// Logging with custom logger
const { request, response } = createLoggingInterceptor(logger);
client.addRequestInterceptor(request);
client.addResponseInterceptor(response);
// Timing metrics
const timing = createTimingInterceptor();
client.addRequestInterceptor(timing.request);
client.addResponseInterceptor(timing.response);
// Retry with custom config
const retry = createRetryInterceptor({
enabled: true,
maxRetries: 5,
retryDelay: 2000,
backoff: 'exponential'
});
client.addResponseInterceptor(retry);
Retry Strategies
Exponential Backoff
Retry with exponentially increasing delays:
{
'http-client': {
globalOptions: {
retry: {
enabled: true,
maxRetries: 3,
retryDelay: 1000,
backoff: 'exponential'
}
}
}
}
Delays: 1s → 2s → 4s
Linear Backoff
Retry with linearly increasing delays:
{
'http-client': {
globalOptions: {
retry: {
enabled: true,
maxRetries: 3,
retryDelay: 1000,
backoff: 'linear'
}
}
}
}
Delays: 1s → 2s → 3s
Custom Retry Condition
Define custom logic to determine if request should be retried:
{
'http-client': {
globalOptions: {
retry: {
enabled: true,
maxRetries: 3,
retryCondition: (error) => {
// Retry only on network errors or 503
return error.isNetworkError || error.statusCode === 503;
}
}
}
}
}
Circuit Breaker Pattern
Prevent cascading failures by opening circuit after threshold failures:
{
'http-client': {
globalOptions: {
circuitBreaker: {
enabled: true,
threshold: 5, // Open after 5 failures
timeout: 60000, // Keep open for 60s
resetTimeout: 30000 // Try to close after 30s
}
}
}
}
Circuit States
// Check circuit state
const state = client.getCircuitState();
console.log(state); // 'CLOSED', 'OPEN', or 'HALF_OPEN'
// Manually reset circuit
client.resetCircuitBreaker();
Dynamic Client Creation
Create clients dynamically at runtime:
import { HTTPClient, HTTPClientConfig } from '@stratix/http-client';
class ApiService {
constructor(
private readonly clientFactory: (config: HTTPClientConfig) => HTTPClient
) {}
createClientForTenant(tenantId: string) {
return this.clientFactory({
baseURL: `https://${tenantId}.api.example.com`,
timeout: 5000,
headers: {
'X-Tenant-ID': tenantId
}
});
}
async getTenantData(tenantId: string) {
const client = this.createClientForTenant(tenantId);
const response = await client.get('/data');
return response.data;
}
}
// Register in container
container.register(
'apiService',
(c) => new ApiService(c.resolve('http:clientFactory'))
);
Request Cancellation
Cancel requests using AbortController:
const controller = new AbortController();
const request = client.get('/users', {
signal: controller.signal
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const response = await request;
} catch (error) {
if (error.code === 'ERR_CANCELED') {
console.log('Request was cancelled');
}
}
File Upload
Upload files with multipart/form-data:
const formData = new FormData();
formData.append('file', fileBlob, 'document.pdf');
formData.append('description', 'Important document');
const response = await client.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 30000 // Longer timeout for uploads
});
Download Files
Download files with progress tracking:
const response = await client.get('/files/document.pdf', {
responseType: 'blob',
onDownloadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Download: ${percentCompleted}%`);
}
});
// Save blob
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'document.pdf';
link.click();
Streaming Responses
Handle streaming responses:
const response = await client.get('/stream', {
responseType: 'stream'
});
response.data.on('data', (chunk) => {
console.log('Received chunk:', chunk);
});
response.data.on('end', () => {
console.log('Stream ended');
});
Testing
Mocking HTTP Clients
import { HTTPClient } from '@stratix/http-client';
import { vi } from 'vitest';
describe('UserService', () => {
it('should fetch user', async () => {
// Create mock client
const mockClient = {
get: vi.fn().mockResolvedValue({
data: { id: '1', name: 'John' },
status: 200,
statusText: 'OK',
headers: {},
config: {}
})
} as unknown as HTTPClient;
// Inject mock via constructor
const service = new UserService(mockClient);
const user = await service.getUser('1');
expect(user).toEqual({ id: '1', name: 'John' });
expect(mockClient.get).toHaveBeenCalledWith('/users/1');
});
});
Integration Testing
import { ApplicationBuilder } from '@stratix/runtime';
import { AxiosHTTPClientPlugin } from '@stratix/http-client';
describe('HTTP Client Integration', () => {
let app: Application;
beforeAll(async () => {
app = await ApplicationBuilder.create()
.usePlugin(new AxiosHTTPClientPlugin())
.withConfig({
'http-client': {
default: {
baseURL: 'https://jsonplaceholder.typicode.com'
}
}
})
.build();
await app.start();
});
afterAll(async () => {
await app.stop();
});
it('should make real HTTP request', async () => {
const client = app.resolve<HTTPClient>('http:client');
const response = await client.get('/posts/1');
expect(response.status).toBe(200);
expect(response.data).toHaveProperty('id', 1);
});
});
Best Practices
1. Use Named Clients
Create separate clients for different APIs:
{
'http-client': {
clients: {
'users-api': { baseURL: 'https://users.api.com' },
'payments-api': { baseURL: 'https://payments.api.com' },
'notifications-api': { baseURL: 'https://notifications.api.com' }
}
}
}
2. Configure Timeouts
Always set appropriate timeouts:
{
'http-client': {
default: {
timeout: 5000 // 5 seconds for most requests
},
clients: {
'long-running': {
timeout: 30000 // 30 seconds for long operations
}
}
}
}
3. Enable Retries for Transient Failures
{
'http-client': {
globalOptions: {
retry: {
enabled: true,
retryStatusCodes: [408, 429, 500, 502, 503, 504]
}
}
}
}
4. Use Circuit Breaker for External Services
{
'http-client': {
clients: {
'external-api': {
baseURL: 'https://external.api.com'
}
},
globalOptions: {
circuitBreaker: {
enabled: true,
threshold: 5
}
}
}
}
5. Handle Errors Properly
try {
const response = await client.get('/users/123');
} catch (error) {
if (isHTTPResponseError(error)) {
// Handle HTTP errors
if (error.statusCode === 404) {
return null; // User not found
}
if (error.statusCode >= 500) {
// Retry or fallback
}
} else if (isHTTPTimeoutError(error)) {
// Handle timeout
} else if (isHTTPNetworkError(error)) {
// Handle network error
}
throw error;
}
Next Steps
- API Reference - Complete API documentation
- Configuration - Configuration options