Modern, feature-rich HTTP client for Node.js and browsers, built on the native Fetch API. Modular architecture with separate feature packages for optimal bundle size.
Voxa isn't just another HTTP clientβit's a complete request management system with a modular architecture. Install only the features you need, keeping your bundle size minimal while having access to advanced capabilities that Axios, Fetch, and other popular clients simply don't have.
Voxa is split into multiple packages:
- @0xshariq/voxa-core (~140KB) - Essential HTTP client with caching, retry, queue, rate limiting, and deduplication
- @0xshariq/voxa-streaming-images - Image streaming with progress tracking
- @0xshariq/voxa-streaming-videos - Video streaming with progress tracking
- @0xshariq/voxa-http2 - HTTP/2 Server Push support
- @0xshariq/voxa-graphql - GraphQL query support
- @0xshariq/voxa-batch - Request batching
- @0xshariq/voxa-offline - Offline queue management
- @0xshariq/voxa-circuit-breaker - Circuit breaker pattern
- @0xshariq/voxa-token - OAuth/JWT token management
- @0xshariq/voxa-metrics - Performance metrics tracking
- @0xshariq/voxa-cancel - Advanced request cancellation
- Voxa CLI (Under Development)
- Voxa API (Private API only for trusted users and organizations) (Done)
- Voxa SDKs (Go,Rust,Python and Ruby) (Not Planned Yet)
| Feature | Voxa | Axios | Fetch | ky | Got | node-fetch |
|---|---|---|---|---|---|---|
| Bundle Size (Minified) | 21 KB | 32.1 KB | 0KB | 14.6 KB | 180 KB | 18 KB |
| Bundle Size (Gzipped) | 5.8 KB | 12.1 KB | 0KB | 4.8 KB | 52 KB | 6.5 KB |
| Dependencies | 0 | 4 | 0 | 0 | 15 | 0 |
| Modular Architecture | β | β | β | β | β | β |
| TypeScript First | β | β | β | β | β | |
| Browser + Node.js | β | β | β | β | β | β |
| Automatic Retry | β | β | β | β | β | β |
| Response Caching | β Advanced | β | β | β Basic | β Advanced | β |
| Request Deduplication | β | β | β | β | β | β |
| Priority Queue | β | β | β | β | β | β |
| Batch Requests | β | β | β | β | β | β |
| Token Management | β Advanced | β | β | β | β | β |
| Offline Queue | β | β | β | β | β | β |
| GraphQL Support | β | β | β | β | β | β |
| Streaming Progress | β Advanced | β Basic | β Basic | β | β | β |
| Request/Response Interceptors | β | β | β | β | β | β |
| Circuit Breaker | β | β | β | β | β | β |
| Rate Limiting | β | β | β | β | β | β |
| SSRF Protection | β | β | β | β | β | β |
| Debug Mode | β | β | β | β | ||
| Cancel Requests | β | β | β | β | β | β |
| Schema Validation | β | β | β | β | β | β |
| Error Classification | β | β | β | β | ||
| Metadata Tracking | β | β | β | β | β | β |
- π Automatic Retry (exponential backoff)
- πΎ Response Caching (memory/file/custom)
- π― Request Deduplication
- β‘ Priority Queue
- π TypeScript First
- π Interceptors
- π Request Tracking & Metadata
- β±οΈ Timeout Control
- π Static Methods
- π‘ Schema Validation
- π SSRF Protection
- π Debug Mode
- π§ͺ Automatic JSON Parsing
- βοΈ Rate Limiting
Install only what you need:
- πΉ @0xshariq/voxa-streaming-images - Image upload/download with progress
- π¬ @0xshariq/voxa-streaming-videos - Video upload/download with progress
- β‘ @0xshariq/voxa-http2 - HTTP/2 Server Push
- π @0xshariq/voxa-graphql - GraphQL queries
- π¦ @0xshariq/voxa-batch - Request batching
- π΄ @0xshariq/voxa-offline - Offline queue
- π @0xshariq/voxa-circuit-breaker - Circuit breaker pattern
- π« @0xshariq/voxa-token - OAuth/JWT token management
- π @0xshariq/voxa-metrics - Performance metrics
- π @0xshariq/voxa-cancel - Advanced cancellation
npm install @0xshariq/voxa-core
# or
pnpm install @0xshariq/voxa-core
# or
yarn add @0xshariq/voxa-core# Install only the features you need
npm install @0xshariq/voxa-graphql @0xshariq/voxa-streaming-imagesimport { Voxa } from "@0xshariq/voxa-core";
const client = new Voxa({
baseURL: "https://api.example.com",
timeout: 5000,
cache: {
enabled: true,
ttl: 60000,
},
retry: {
enabled: true,
count: 3,
},
});
const response = await client.get("/users");
console.log(response.data);import { Voxa } from "@0xshariq/voxa-core";
import "@0xshariq/voxa-graphql"; // Types auto-merge
import "@0xshariq/voxa-batch";
import { StreamingImageManager } from "@0xshariq/voxa-streaming-images";
const client = new Voxa({
baseURL: "https://api.example.com",
graphql: {
// TypeScript knows about this!
enabled: true,
endpoint: "/graphql",
},
batch: {
enabled: true,
wait: 100,
},
});
// Use streaming
const imageManager = new StreamingImageManager();
await imageManager.upload("/upload", imageFile, {}, (sent, total) => {
console.log(`Progress: ${((sent / total) * 100).toFixed(2)}%`);
});import { Voxa } from "@0xshariq/voxa-core";
const api = new Voxa({
baseURL: "https://api.example.com",
timeout: 5000,
headers: {
"Content-Type": "application/json", // string
Authorization: "Bearer <token>", // string
},
priority: "high", // 'critical' | 'high' | 'normal' | 'low'
retry: {
enabled: true, // boolean (default: true)
count: 5, // number (max: 5)
delay: 1000, // number (ms)
exponentialBackoff: true, // boolean
maxRetry: 10000, // number (ms)
statusCodes: [429, 500, 502, 503, 504], // number[]
},
deduplication: {
enabled: true, // boolean (default: true)
ttl: 300000, // number (ms)
},
cache: {
enabled: true, // boolean (default: true)
ttl: 300000, // number (ms, default: 5 min)
storage: "memory", // 'memory' | 'custom'
adapter: undefined, // custom cache adapter (optional)
},
queue: {
enabled: true, // boolean
maxConcurrent: 5, // number
},
batch: {
enabled: true, // boolean
endpoint: "/batch", // string (optional)
wait: 100, // number (ms, optional)
maxBatchSize: 10, // number (optional)
},
token: {
enabled: true, // boolean
type: "bearer", // 'bearer' | 'oauth2' | 'jwt'
tokenEndpoint: "/auth/token", // string
clientId: "client-id", // string
clientSecret: "client-secret", // string
refreshEndpoint: "/auth/refresh", // string
storage: "memory", // 'memory' | 'localStorage'
getToken: async () => "token", // function
setToken: (token: string) => {}, // function
refreshToken: async () => "new-token", // function
},
// Token refresh failure hook
onTokenRefreshFailure: (error, context) => {
// Custom logic: log, alert, or trigger re-authentication
console.error("Token refresh failed:", error, context);
// Optionally, redirect user or clear session
},
// Multi-tenant/multi-user support example
getToken: async (userId) => {
// Fetch token for a specific user/tenant
return await fetchTokenForUser(userId);
},
setToken: (token, userId) => {
// Store token for a specific user/tenant
saveTokenForUser(token, userId);
},
refreshToken: async (userId) => {
// Refresh token for a specific user/tenant
return await refreshUserToken(userId);
},
// Usage:
// await api.get('/resource', { userId: 'user-42' });
offline: {
enabled: true, // boolean (default: true)
storage: "localStorage", // 'localStorage' | 'indexedDB'
},
circuitBreaker: {
enabled: true, // boolean
threshold: 5, // number
timeout: 10000, // number (ms)
onOpen: () => {}, // function (optional)
},
metrics: {
enabled: true, // boolean
},
errors: {
enabled: true, // boolean (default: true)
},
rate: {
enabled: true, // boolean (default: true)
maxRequests: 100, // number
perMilliseconds: 60000, // number (ms)
},
schema: {
enabled: true, // boolean
requestSchema: undefined, // any (optional)
responseSchema: undefined, // any (optional)
library: "zod", // 'zod' | 'yup' (optional)
},
cancel: {
enabled: true, // boolean (default: true)
},
graphql: {
enabled: true, // boolean
endpoint: "https://graphqlzero.almansi.me/api", // string
logErrors: true, // boolean
headers: undefined, // Record<string, string> (optional)
timeout: undefined, // number (optional)
cache: undefined, // boolean (optional)
},
interceptors: {
request: [
(config) => {
/* modify config */ return config;
},
],
response: [
(response) => {
/* log/modify response */ return response;
},
],
},
metadata: {
enabled: true, // boolean (default: true)
log: true, // boolean (log metadata events)
fields: [
"id",
"method",
"endpoint",
"priority",
"timestamp",
"startTime",
"endTime",
], // string[] (fields to track)
maxEntries: 100, // number (max entries to keep)
customHandler: (meta) => {}, // function (custom handler)
},
});Voxa's features (cache, retry, queue, batch, deduplication, interceptors, etc.) are fully modular and can be enabled, disabled, or extended at runtime.
You can toggle features on/off by updating the instance configuration:
// Disable cache and queue at runtime
api.updateConfig({
cache: { enabled: false },
queue: { enabled: false },
});
// Enable retry and set new retry count
api.updateConfig({
retry: { enabled: true, count: 10 },
});Note: Not all features support dynamic reconfiguration in-flight. For critical changes, create a new instance with the desired config.
You can add your own features or override built-in ones by attaching custom managers or hooks:
// Example: Add a custom logging plugin
api.usePlugin({
onRequest(config) {
console.log("Request:", config.url);
return config;
},
onResponse(response) {
console.log("Response:", response.status);
return response;
},
});A plugin is an object with any of these hooks:
onRequest(config)onResponse(response)onError(error)onBatch(batch)onCacheEvent(event)
You can register multiple plugins. They are called in the order added.
// Disable cache for a single request
await api.get('/users', { cache: { enabled: false } });
// Set custom queue priority for a single request
await api.post('/orders', { ... }, { priority: 'critical' });See Advanced Features for more on feature modularity and plugins.
RequestId will generate automatically and will use in features (like retry,batching,etc..):
// Provide a custom requestId and metadata for tracing
const response = await api.get("/users/1", {
requestId: "trace-abc-123", // requestid will generate per request
metadata: {
traceId: "trace-abc-123",
userId: "user-42",
customField: "my-value",
},
});
console.log(response.metadata); // includes your custom fieldsNote: The
requestIdgenerates automatically and use in all features.
Create a client instance to reuse configuration and advanced features:
import voxa from "@0xshariq/voxa";
const api = voxa.create({
baseURL: "https://api.example.com",
timeout: 5000,
// ...other options
});
// Make requests (response is always parsed JSON)
const response = await api.get<User>("/users/1");
console.log(response.data); // { id, name, ... }
// You can also use other HTTP methods:
await api.post("/users", { name: "John" });
await api.put("/users/1", { name: "Jane" });
await api.delete("/users/1");Call static methods directly for one-off requests:
import { Voxa } from "@0xshariq/voxa";
const response = await Voxa.get<User>("https://api.example.com/users/1");
console.log(response.data);
// Other static methods:
await Voxa.post("https://api.example.com/users", { name: "John" });
await Voxa.put("https://api.example.com/users/1", { name: "Jane" });
await Voxa.delete("https://api.example.com/users/1");Use an instance for advanced features (caching, queueing, interceptors, GraphQL, etc.), or static methods for simple requests.
interface User {
id: number;
name: string;
email: string;
}
// Type-safe responses
const response = await api.get<User>("/users/1");
const user: User = await response.json();import { StreamingImageManager, StreamingVideoManager } from "@0xshariq/voxa";
// Upload image with progress tracking
const streamingImages = new StreamingImageManager({});
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
await streamingImages.upload(
"https://api.example.com/images/upload",
file,
{ "Content-Type": "image/jpeg" },
(sentBytes, totalBytes) => {
const percentage = (sentBytes / totalBytes) * 100;
console.log(`Upload progress: ${percentage.toFixed(2)}%`);
}
);
// Download video with progress
const streamingVideos = new StreamingVideoManager({});
const response = await streamingVideos.download(
"https://api.example.com/videos/12345",
{},
(receivedBytes, totalBytes) => {
console.log(`Downloaded: ${receivedBytes}/${totalBytes} bytes`);
}
);
const blob = await response.blob();
const videoUrl = URL.createObjectURL(blob);See Streaming Guide for complete examples and advanced usage.
Documentation:
- Configuration Guide β All config options.
- Advanced Features β Caching, deduplication, queueing, interceptors, GraphQL, etc.
- Streaming Guide β Image/video upload/download with progress tracking.
- Developer Experience β Debug mode, logging, public getters, type safety.
- Troubleshooting β Common issues and solutions.
- Custom Cache β Custom cache implementation.
- Batch Usage β Batch request patterns.
- Examples β Usage patterns and code samples.
- Migration - Migration from axios to voxa.
See docs/ for full details and up-to-date usage.
const api = voxa.create({
baseURL: "https://api.example.com",
retry: {
count: 3, // Max 3 retries
delay: 1000, // Initial delay: 1s
exponentialBackoff: true, // 1s β 2s β 4s
maxRetry: 10000, // Max delay: 10s
},
});{
response: Response, // Http Response object
data: {}, // Api Data will store here
metadata: {
id: "string", // requestId used everywhere
method: "'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'",
endpoint: "string",
priority: "'critical' | 'high' | 'normal' | 'low'",
timestamp: 2000,
startTime: 2000,
endTime: 2000
},
requestId: string, // Unique requestId, used in all features (queue, batch, cache, metrics, etc.)
status: 200,
statusText: "Ok"
}See Response Structure for detailed info.
// Memory cache (default)
const api = voxa.create({
cache: {
enabled: true,
type: "memory",
ttl: 300000, // 5 minutes
},
});
// Redis cache
const api = voxa.create({
cache: {
enabled: true,
type: "redis",
ttl: 300000,
},
});// High priority request
await api.get("/critical-data", { priority: "critical" });
// Normal priority (default)
await api.get("/regular-data");
// Low priority
await api.get("/background-data", { priority: "low" });// Add authentication header
api.interceptors.request.use((config) => {
config.headers = {
...config.headers,
Authorization: `Bearer ${getToken()}`,
};
return config;
});
// Log responses
api.interceptors.response.use((response) => {
console.log("Response:", response.status);
return response;
});Create a .env file in your project root:
# Cache Configuration (Generic - works with any cache backend)
CACHE_URL=redis://localhost:6379
CACHE_PASSWORD=your-password
CACHE_HOST=localhost
CACHE_PORT=6379
CACHE_DB=0
# HTTP Configuration
HTTP_TIMEOUT=5000
HTTP_BASE_URL=https://api.example.comSee Configuration Guide for detailed options.
// Get cache statistics
const cacheStats = api.getCacheStats();
console.log(cacheStats); // { storage: 'memory', size: 10, entries: [...] }
// Get queue statistics
const queueStats = api.getQueueStats();
console.log(queueStats); // { queueSize: 2, activeRequests: 3, maxConcurrent: 5 }
// Get request metadata
const metadata = api.getRequestMetadata("request-id-123");
console.log(metadata); // { id, method, endpoint, duration, ... }Each request is assigned a unique requestId for tracking and feature management. The format is:
<timestamp>-<expiry>-<random>
-
timestamp: When the request was created -
expiry: When the requestId expires (used for cache/batch conflict avoidance) -
random: Random string for uniqueness -
Cache feature uses a 5 minute expiry (default).
-
Batch feature uses a 15 minute expiry.
This prevents conflicts when the same request is sent at different times. Always use the generated requestId for all features (cache, batch, queue, etc.).
Voxa now provides detailed error classification and debugging messages for every request. Use api.classifyError(error) to get both the error category and a helpful message for easier troubleshooting.
All feature managers (cache, queue, deduplication, metadata, circuit breaker, batch, rate limiter, metrics, schema, error classifier) are accessible via public methods or stats getters:
api.getCacheStats()api.getQueueStats()api.getDeduplicationStats()api.getMetadataStats()api.circuitBreaker()api.batch()api.rate()api.metrics()api.schema()api.classifyError(error)
# Run test file directly
pnpm test
# Or using development mode
pnpm devVoxa supports all standard HTTP methods:
await api.get("/users");
await api.post("/users", { name: "John" });
await api.put("/users/1", { name: "Jane" });
await api.patch("/users/1", { email: "[email protected]" });
await api.delete("/users/1");
await api.head("/users");
await api.options("/users");Contributions are welcome! Please feel free to submit a Pull Request.
See the Contributing Guide
MIT Β© Sharique Chaudhary
Note: This project is built on the native Fetch API and requires Node.js 18+ or a modern browser environment.