Zero-dependency HTTP client with retries, caching, circuit breakers, and ETags. Single file, no build tools required.
Size: 18KB minified, 5KB gzipped
<script src="grab.min.js"></script>
<script>
const api = new Grab({ baseUrl: 'https://api.example.com' });
const response = await api.get('/users');
console.log(response.data);
</script>
- Smart Retries with exponential backoff
- Response Caching with ETag support
- Circuit Breaker for fault tolerance
- Request Deduplication prevents duplicate in-flight requests
- Auth-Aware Cache prevents user data leakage
- Priority Hints for modern browsers
- HTTP/2 Push hint processing
const api = new Grab({
baseUrl: 'https://api.example.com',
timeout: 30000,
headers: { 'Authorization': 'Bearer token' },
cache: {
enabled: true,
ttl: 5 * 60 * 1000, // 5 minutes
maxSize: 100
},
retry: {
attempts: 3,
delay: (attempt) => Math.min(1000 * Math.pow(2, attempt - 1), 30000),
condition: (error) => error.status >= 500
},
circuitBreaker: {
failureThreshold: 5,
resetTimeout: 60000,
fallback: () => ({ error: 'Service unavailable' })
}
});
// Basic requests
await api.get('/users');
await api.post('/users', { body: userData });
await api.put('/users/123', { body: updateData });
await api.delete('/users/123');
// Query parameters
await api.get('/search', { params: { q: 'javascript', page: 1 } });
// Custom headers
await api.post('/upload', {
body: formData,
headers: { 'Content-Type': 'multipart/form-data' }
});
// Response types
await api.get('/data.json', { responseType: 'json' });
await api.get('/image.png', { responseType: 'blob' });
// Priority hints
await api.get('/critical', { priority: 'high' });
// Add auth to all requests
api.use({
request: (config) => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
}
});
// Log responses
api.use({
response: (response) => {
console.log(`${response.status} ${response.url}`);
return response;
}
});
// Handle auth errors
api.use({
error: (error) => {
if (error.status === 401) {
window.location.href = '/login';
}
throw error;
}
});
try {
const response = await api.get('/data');
} catch (error) {
if (error instanceof HttpError) {
console.error(`HTTP ${error.status}: ${error.message}`);
} else if (error instanceof NetworkError) {
console.error('Network issue:', error.message);
} else if (error instanceof TimeoutError) {
console.error(`Timeout after ${error.timeout}ms`);
}
}
// JSON requests (auto-sets Content-Type)
const userData = await api.json('POST', '/users', {
name: 'Bob',
email: 'bob@example.com'
});
// Form data
const result = await api.form('POST', '/upload', {
file: fileInput.files[0],
description: 'My file'
});
// Check stats
console.log(api.getCacheStats());
// { size: 10, maxSize: 100, ttl: 300000, pending: 0, etags: 5 }
// Clear cache
api.clearCache();
// Invalidate specific entries
api.invalidateCache('/users.*');
// Disable caching per request
await api.get('/real-time', { cache: false });
// Check health
console.log(api.isHealthy());
// Get stats
const stats = api.getCircuitBreakerStats();
// { state: 'CLOSED', failures: 0, successes: 42, isHealthy: true }
// Reset manually
api.resetCircuitBreaker();
Requires modern browsers with fetch()
support. For IE11, add polyfills:
<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch,AbortController"></script>
<script src="grab.min.js"></script>
If using Node, requires Node.js 18+ (for built-in fetch) or add polyfill:
// Node 18+
const { Grab } = require('./Grab.js');
// Older Node
global.fetch = require('node-fetch');
const { Grab } = require('./Grab.js');
MIT—Do whatever you want with it.