⚠️ ALPHA SOFTWARE WARNING⚠️ This software is in early alpha development and should NOT be used in production environments. Features may be incomplete, unstable, or subject to breaking changes without notice. Test coverage and documentation are still under development.
@fluxhttp/core is an experimental HTTP client library for JavaScript/TypeScript with zero dependencies and full TypeScript support. This project is under active development with the goal of creating a modern, lightweight alternative to existing HTTP clients.
- Installation
- Quick Start
- Core API
- Configuration
- Interceptors
- Error Handling
- Adapters
- TypeScript Support
- Advanced Features
- Migration from Axios
npm install @fluxhttp/core
# or
yarn add @fluxhttp/core
# or
pnpm add @fluxhttp/core
# or
bun add @fluxhttp/coreimport fluxhttp from '@fluxhttp/core';
// GET request
const response = await fluxhttp.get('https://api.example.com/users');
console.log(response.data);
// POST request
const user = await fluxhttp.post('https://api.example.com/users', {
name: 'John Doe',
email: 'john@example.com'
});
// With async/await
try {
const response = await fluxhttp.get('/users/1');
console.log(response.data);
} catch (error) {
console.error('Error:', error.message);
}import { create } from '@fluxhttp/core';
const apiClient = create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Authorization': 'Bearer your-token-here',
'Content-Type': 'application/json'
}
});
const users = await apiClient.get('/users');Generic request method that all other methods use internally.
const response = await fluxhttp.request({
method: 'GET',
url: '/users',
headers: { 'Accept': 'application/json' }
});Performs a GET request.
// Simple GET
const users = await fluxhttp.get('/users');
// GET with query parameters
const filteredUsers = await fluxhttp.get('/users', {
params: { status: 'active', page: 1 }
});
// GET with custom headers
const user = await fluxhttp.get('/users/1', {
headers: { 'Accept': 'application/vnd.api+json' }
});post<T>(url: string, data?: RequestBody, config?: fluxhttpRequestConfig): Promise<fluxhttpResponse<T>>
Performs a POST request.
// JSON POST
const newUser = await fluxhttp.post('/users', {
name: 'John Doe',
email: 'john@example.com'
});
// Form data POST
const formData = new FormData();
formData.append('file', fileBlob);
const upload = await fluxhttp.post('/upload', formData);
// URL-encoded POST
const params = new URLSearchParams();
params.append('username', 'johndoe');
params.append('password', 'secret');
const login = await fluxhttp.post('/login', params);put<T>(url: string, data?: RequestBody, config?: fluxhttpRequestConfig): Promise<fluxhttpResponse<T>>
Performs a PUT request.
const updatedUser = await fluxhttp.put('/users/1', {
name: 'Jane Doe',
email: 'jane@example.com'
});patch<T>(url: string, data?: RequestBody, config?: fluxhttpRequestConfig): Promise<fluxhttpResponse<T>>
Performs a PATCH request.
const partialUpdate = await fluxhttp.patch('/users/1', {
email: 'newemail@example.com'
});Performs a DELETE request.
await fluxhttp.delete('/users/1');Performs a HEAD request.
const headers = await fluxhttp.head('/users/1');
console.log(headers.headers['last-modified']);Performs an OPTIONS request.
const options = await fluxhttp.options('/api');
console.log(options.headers['allow']);Creates a new fluxhttp instance with custom configuration.
import { create } from '@fluxhttp/core';
const apiClient = create({
baseURL: 'https://api.example.com',
timeout: 10000
});Helper for executing multiple requests concurrently.
import { all } from '@fluxhttp/core';
const [users, posts, comments] = await all([
fluxhttp.get('/users'),
fluxhttp.get('/posts'),
fluxhttp.get('/comments')
]);Helper for spreading array results to function arguments.
import { spread } from '@fluxhttp/core';
fluxhttp.all([
fluxhttp.get('/users'),
fluxhttp.get('/posts')
]).then(spread((users, posts) => {
console.log('Users:', users.data);
console.log('Posts:', posts.data);
}));Type guard to check if an error is an fluxhttp error.
import { isfluxhttpError } from '@fluxhttp/core';
try {
await fluxhttp.get('/api/data');
} catch (error) {
if (isfluxhttpError(error)) {
console.log('Status:', error.response?.status);
console.log('Message:', error.message);
}
}Checks if an error was caused by request cancellation.
import { isCancel } from '@fluxhttp/core';
try {
await fluxhttp.get('/api/data');
} catch (error) {
if (isCancel(error)) {
console.log('Request was cancelled');
}
}Complete configuration interface for customizing requests:
interface fluxhttpRequestConfig {
// Core options
url?: string;
method?: HttpMethod;
baseURL?: string;
headers?: Headers;
params?: QueryParams;
data?: RequestBody;
// Network options
timeout?: number;
withCredentials?: boolean;
auth?: {
username: string;
password: string;
};
// Response options
responseType?: 'json' | 'text' | 'blob' | 'arraybuffer' | 'stream';
responseEncoding?: string;
validateStatus?: (status: number) => boolean;
// Advanced options
maxRedirects?: number;
maxContentLength?: number;
maxBodyLength?: number;
decompress?: boolean;
signal?: AbortSignal;
// Progress tracking ⚠️ Coming Soon
onUploadProgress?: (progressEvent: ProgressEvent) => void;
onDownloadProgress?: (progressEvent: ProgressEvent) => void;
// Retry configuration ⚠️ Coming Soon
retry?: RetryConfig;
// Cache configuration ⚠️ Coming Soon
cache?: CacheConfig;
// Data transformation
transformRequest?: Array<(data: unknown, headers?: Headers) => unknown>;
transformResponse?: Array<(data: unknown) => unknown>;
// Custom adapter
adapter?: <T = unknown>(config: fluxhttpRequestConfig) => Promise<fluxhttpResponse<T>>;
}const defaults = {
timeout: 0, // No timeout
headers: {
'Accept': 'application/json, text/plain, */*',
'User-Agent': 'fluxhttp/0.1.0'
},
validateStatus: (status: number) => status >= 200 && status < 300,
transformRequest: [
(data, headers) => {
if (isFormData(data) || isURLSearchParams(data)) {
return data;
}
if (isPlainObject(data)) {
headers['content-type'] = 'application/json';
return JSON.stringify(data);
}
return data;
}
],
transformResponse: [
(data) => {
if (typeof data === 'string') {
try {
return JSON.parse(data);
} catch {
return data;
}
}
return data;
}
]
};// Timeout configuration
const client = create({
timeout: 5000 // 5 seconds
});
// Authentication
const authClient = create({
auth: {
username: 'user',
password: 'pass'
}
});
// Custom headers
const apiClient = create({
headers: {
'Authorization': 'Bearer token',
'X-API-Version': '2.0'
}
});
// Response validation
const strictClient = create({
validateStatus: (status) => status === 200
});Interceptors allow you to transform requests and responses globally.
// Add a request interceptor
fluxhttp.interceptors.request.use(
(config) => {
// Add auth token to all requests
config.headers = config.headers || {};
config.headers['Authorization'] = `Bearer ${getToken()}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Conditional interceptor
fluxhttp.interceptors.request.use(
(config) => {
// Only add timestamp to POST requests
if (config.method === 'POST') {
config.data = {
...config.data,
timestamp: Date.now()
};
}
return config;
},
undefined,
{
runWhen: (config) => config.method === 'POST'
}
);// Add a response interceptor
fluxhttp.interceptors.response.use(
(response) => {
// Transform all responses
console.log('Response received:', response.status);
return response;
},
(error) => {
// Handle errors globally
if (error.response?.status === 401) {
// Redirect to login
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// Data transformation interceptor
fluxhttp.interceptors.response.use(
(response) => {
// Add metadata to all responses
response.data = {
...response.data,
meta: {
timestamp: Date.now(),
source: 'fluxhttp'
}
};
return response;
}
);// Remove interceptor
const interceptorId = fluxhttp.interceptors.request.use(config => config);
fluxhttp.interceptors.request.eject(interceptorId);
// Clear all interceptors
fluxhttp.interceptors.request.clear();
fluxhttp.interceptors.response.clear();
// Multiple interceptors
const requestId1 = fluxhttp.interceptors.request.use(addAuth);
const requestId2 = fluxhttp.interceptors.request.use(addTimestamp);
const responseId1 = fluxhttp.interceptors.response.use(logResponse);fluxhttp provides a comprehensive error interface:
interface fluxhttpError extends Error {
config?: fluxhttpRequestConfig;
code?: string;
request?: unknown;
response?: fluxhttpResponse;
isfluxhttpError: boolean;
toJSON: () => Record<string, unknown>;
}// Network errors
try {
await fluxhttp.get('https://unreachable.com');
} catch (error) {
if (error.code === 'ERR_NETWORK') {
console.log('Network error occurred');
}
}
// Timeout errors
try {
await fluxhttp.get('/slow-endpoint', { timeout: 1000 });
} catch (error) {
if (error.code === 'ETIMEDOUT') {
console.log('Request timed out');
}
}
// HTTP status errors
try {
await fluxhttp.get('/not-found');
} catch (error) {
if (error.response?.status === 404) {
console.log('Resource not found');
}
}
// Cancelled requests
try {
const controller = new AbortController();
setTimeout(() => controller.abort(), 100);
await fluxhttp.get('/data', { signal: controller.signal });
} catch (error) {
if (error.code === 'ERR_CANCELED') {
console.log('Request was cancelled');
}
}try {
await fluxhttp.get('/api/users/invalid');
} catch (error: fluxhttpError) {
console.log({
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
headers: error.response?.headers,
config: {
url: error.config?.url,
method: error.config?.method,
headers: error.config?.headers
}
});
}fluxhttp automatically selects the appropriate adapter based on the environment:
- XMLHttpRequest: Primary adapter for browsers
- Fetch: Fallback adapter for modern browsers
- HTTP/HTTPS: Native Node.js adapter with full feature support
// Custom adapter implementation
const customAdapter = async (config: fluxhttpRequestConfig) => {
// Your custom HTTP implementation
const response = await fetch(config.url!, {
method: config.method,
headers: config.headers as HeadersInit,
body: config.data as BodyInit
});
return {
data: await response.json(),
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
config,
request: undefined
};
};
// Use custom adapter
const client = create({
adapter: customAdapter
});fluxhttp is built with TypeScript and provides complete type safety:
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
// Typed responses
const user = await fluxhttp.get<ApiResponse<User>>('/users/1');
console.log(user.data.data.name); // Fully typed
const users = await fluxhttp.get<ApiResponse<User[]>>('/users');
users.data.data.forEach(user => {
console.log(user.email); // Type-safe access
});interface CustomConfig extends fluxhttpRequestConfig {
customProperty?: string;
}
const customClient = create<CustomConfig>({
baseURL: 'https://api.example.com',
customProperty: 'value'
});import { isfluxhttpError, fluxhttpError } from '@fluxhttp/core';
async function fetchUser(id: number) {
try {
const response = await fluxhttp.get<User>(`/users/${id}`);
return response.data;
} catch (error: unknown) {
if (isfluxhttpError(error)) {
// error is now typed as fluxhttpError
console.log('Status:', error.response?.status);
console.log('Config:', error.config?.url);
}
throw error;
}
}const client = create({
transformRequest: [
(data, headers) => {
// Add API version to all requests
if (data && typeof data === 'object') {
return { ...data, apiVersion: '2.0' };
}
return data;
},
(data) => {
// Convert to JSON
return JSON.stringify(data);
}
],
transformResponse: [
(data) => {
// Parse JSON
if (typeof data === 'string') {
try {
return JSON.parse(data);
} catch {
return data;
}
}
return data;
},
(data) => {
// Add client-side metadata
if (data && typeof data === 'object') {
return {
...data,
clientTimestamp: Date.now()
};
}
return data;
}
]
});// Upload progress - FEATURE UNDER DEVELOPMENT
await fluxhttp.post('/upload', formData, {
onUploadProgress: (progressEvent) => {
const progress = (progressEvent.loaded / progressEvent.total!) * 100;
console.log(`Upload: ${progress.toFixed(2)}%`);
}
});
// Download progress - FEATURE UNDER DEVELOPMENT
await fluxhttp.get('/large-file', {
onDownloadProgress: (progressEvent) => {
const progress = (progressEvent.loaded / progressEvent.total!) * 100;
console.log(`Download: ${progress.toFixed(2)}%`);
}
});// Using AbortController
const controller = new AbortController();
const promise = fluxhttp.get('/data', {
signal: controller.signal
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const response = await promise;
} catch (error) {
if (isCancel(error)) {
console.log('Request cancelled');
}
}// Promise.all with fluxhttp.all
const responses = await fluxhttp.all([
fluxhttp.get('/users'),
fluxhttp.get('/posts'),
fluxhttp.get('/comments')
]);
// Using Promise.allSettled for mixed results
const results = await Promise.allSettled([
fluxhttp.get('/endpoint1'),
fluxhttp.get('/endpoint2'),
fluxhttp.get('/endpoint3')
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Request ${index} succeeded:`, result.value.data);
} else {
console.log(`Request ${index} failed:`, result.reason.message);
}
});// FEATURE UNDER DEVELOPMENT
const cache = new Map();
const cachedClient = create({
baseURL: 'https://api.example.com'
});
cachedClient.interceptors.request.use(config => {
const key = `${config.method}:${config.url}`;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < 300000) { // 5 minutes
config.cached = cached.data;
}
return config;
});
cachedClient.interceptors.response.use(response => {
const key = `${response.config.method}:${response.config.url}`;
cache.set(key, {
data: response.data,
timestamp: Date.now()
});
return response;
});fluxhttp provides a nearly identical API to Axios, making migration straightforward:
// Axios
import axios from 'axios';
// fluxhttp
import fluxhttp from '@fluxhttp/core';
// Most code works without changes
const response = await fluxhttp.get('/users');
const newUser = await fluxhttp.post('/users', userData);// Axios
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
});
// fluxhttp
const apiClient = fluxhttp.create({
baseURL: 'https://api.example.com',
timeout: 10000
});// Axios interceptors work the same in fluxhttp
apiClient.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${token}`;
return config;
});
apiClient.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// Handle auth error
}
return Promise.reject(error);
}
);- Zero Dependencies: fluxhttp has no dependencies vs Axios's several
- TypeScript First: Better TypeScript support out of the box
- Modern Standards: Built for modern JavaScript/TypeScript
- Smaller Bundle: Significantly smaller bundle size
- Tree Shaking: Better support for tree shaking
- Native ESM: Full ESM support
- Modern Error Handling: Enhanced error objects
- Better TypeScript: Improved type inference
- Platform Detection: Automatic adapter selection
// Always use try-catch with async/await
try {
const response = await fluxhttp.get('/api/data');
return response.data;
} catch (error) {
if (isfluxhttpError(error)) {
// Handle HTTP errors
console.error('HTTP Error:', error.response?.status);
} else {
// Handle other errors
console.error('Unknown Error:', error);
}
throw error;
}// Create separate clients for different APIs
const apiClient = fluxhttp.create({
baseURL: 'https://api.example.com',
timeout: 10000
});
const authClient = fluxhttp.create({
baseURL: 'https://auth.example.com',
timeout: 5000
});// Always define response types
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
}
const fetchUsers = async (): Promise<User[]> => {
const response = await fluxhttp.get<ApiResponse<User[]>>('/users');
return response.data.data;
};// Use concurrent requests for independent data
const [users, posts, settings] = await Promise.all([
fluxhttp.get('/users'),
fluxhttp.get('/posts'),
fluxhttp.get('/settings')
]);
// Cancel requests when components unmount
useEffect(() => {
const controller = new AbortController();
fluxhttp.get('/data', { signal: controller.signal })
.then(setData)
.catch(error => {
if (!isCancel(error)) {
setError(error);
}
});
return () => controller.abort();
}, []);This completes the API documentation for @fluxhttp/core.