nanoreq
v1.0.0
Published
Lightweight HTTP client library - tiny fetch wrapper with Axios-like API
Maintainers
Readme
NanoReq
⚡ Lightweight HTTP client library - A tiny fetch wrapper with an Axios-like API
Features
- 🪶 Tiny bundle size - Zero dependencies, minimal overhead
- 🌲 Tree-shakeable - Import only what you need
- 🔄 Interceptors - Request and response interceptors support
- 🎯 TypeScript first - Full TypeScript support with proper types
- 🌐 Universal - Works in Node.js and browsers
- ⚡ Modern - Built on fetch API
- 📦 Dual format - ESM and CJS exports
Installation
npm install nanoreqQuick Start
import { nanoReq } from 'nanoreq';
// Simple GET request
const response = await nanoReq.get('https://api.example.com/users');
console.log(response.data);
// POST request with data
const newUser = await nanoReq.post('https://api.example.com/users', {
name: 'John Doe',
email: '[email protected]',
});API Reference
Basic Methods
// GET request
nanoReq.get(url, config?)
// POST request
nanoReq.post(url, data?, config?)
// PUT request
nanoReq.put(url, data?, config?)
// PATCH request
nanoReq.patch(url, data?, config?)
// DELETE request
nanoReq.delete(url, config?)
// Generic request
nanoReq.request(config)Creating Custom Instances
Create instances with default configuration (like axios.create):
import { createNanoReq } from 'nanoreq';
const api = createNanoReq({
baseURL: 'https://api.example.com',
headers: {
Authorization: 'Bearer YOUR_TOKEN',
'X-Custom-Header': 'value',
},
timeout: 5000,
});
// Now use the custom instance
const users = await api.get('/users');
const user = await api.get('/users/1');Configuration Options
interface RequestConfig {
baseURL?: string; // Base URL for requests
url?: string; // Request URL
method?: string; // HTTP method
headers?: Record<string, string>; // Request headers
params?: Record<string, any>; // URL query parameters
data?: any; // Request body data
timeout?: number; // Request timeout in ms
rateLimit?: {
// Rate limiting (per instance)
maxRequests: number; // Max requests in time window
perMilliseconds: number; // Time window in ms
};
retry?: {
// Retry configuration
count?: number; // Max retry attempts
delay?: number; // Delay between retries
exponentialBackoff?: boolean; // Enable exponential backoff
};
validateStatus?: (status: number) => boolean; // Custom status validator
debug?: boolean; // Enable debug logging
}Response Object
interface NanoReqResponse<T = any> {
data: T; // Response data (auto-parsed JSON)
status: number; // HTTP status code
statusText: string; // HTTP status text
headers: Headers; // Response headers
config: RequestConfig; // Request configuration used
}Error Object
interface NanoReqError extends Error {
message: string; // Error message
status?: number; // HTTP status code (if available)
url?: string; // Request URL
method?: string; // HTTP method
data?: any; // Error response data
originalError?: Error; // Original error object
}Examples
Basic GET Request
import { nanoReq } from 'nanoreq';
try {
const response = await nanoReq.get('https://jsonplaceholder.typicode.com/posts/1');
console.log(response.data);
} catch (error) {
console.error('Error:', error.message);
}POST Request with Data
const response = await nanoReq.post('https://jsonplaceholder.typicode.com/posts', {
title: 'My Post',
body: 'This is the content',
userId: 1,
});
console.log('Created:', response.data);Using Query Parameters
const response = await nanoReq.get('https://api.example.com/search', {
params: {
q: 'javascript',
page: 1,
limit: 10,
},
});
// Requests: https://api.example.com/search?q=javascript&page=1&limit=10Custom Headers
const response = await nanoReq.get('https://api.example.com/protected', {
headers: {
Authorization: 'Bearer YOUR_TOKEN',
'X-Custom-Header': 'value',
},
});Using BaseURL
import { createNanoReq } from 'nanoreq';
const api = createNanoReq({
baseURL: 'https://api.github.com',
headers: {
Accept: 'application/vnd.github.v3+json',
},
});
// All requests will be relative to baseURL
const repos = await api.get('/users/octocat/repos');
const repo = await api.get('/repos/octocat/hello-world');Request Interceptors
Add custom logic before requests are sent:
import { createNanoReq } from 'nanoreq';
const api = createNanoReq({
baseURL: 'https://api.example.com',
});
// Add authentication token to all requests
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
return config;
});
// Add request logging
api.interceptors.request.use(config => {
console.log(`Making ${config.method} request to ${config.url}`);
return config;
});
// Now all requests will have the token
const response = await api.get('/protected-resource');Response Interceptors
Transform or log responses:
import { createNanoReq } from 'nanoreq';
const api = createNanoReq({
baseURL: 'https://api.example.com',
});
// Transform response data
api.interceptors.response.use(response => {
// Unwrap nested data structure
if (response.data && response.data.data) {
response.data = response.data.data;
}
return response;
});
// Log response times
api.interceptors.request.use(config => {
config.metadata = { startTime: Date.now() };
return config;
});
api.interceptors.response.use(response => {
const duration = Date.now() - response.config.metadata?.startTime;
console.log(`Request took ${duration}ms`);
return response;
});Error Handling
import { nanoReq } from 'nanoreq';
import type { NanoReqError } from 'nanoreq';
try {
const response = await nanoReq.get('https://api.example.com/not-found');
} catch (error) {
const err = error as NanoReqError;
if (err.status === 404) {
console.log('Resource not found');
} else if (err.status === 500) {
console.log('Server error');
} else if (!err.status) {
console.log('Network error:', err.message);
}
console.log('Error details:', {
url: err.url,
method: err.method,
status: err.status,
data: err.data,
});
}Error Interceptors
Handle errors globally:
import { createNanoReq } from 'nanoreq';
const api = createNanoReq({
baseURL: 'https://api.example.com',
});
api.interceptors.response.use(
response => response,
error => {
if (error.status === 401) {
// Redirect to login
window.location.href = '/login';
} else if (error.status >= 500) {
// Show error notification
console.error('Server error:', error.message);
}
throw error;
}
);Timeout
// Set timeout for a specific request
try {
const response = await nanoReq.get('https://api.example.com/slow', {
timeout: 3000, // 3 seconds
});
} catch (error) {
if (error.message.includes('timeout')) {
console.log('Request timed out');
}
}
// Set default timeout for all requests
const api = createNanoReq({
baseURL: 'https://api.example.com',
timeout: 5000,
});Rate Limiting
Prevent API hammering with built-in rate limiting:
const api = createNanoReq({
baseURL: 'https://api.example.com',
rateLimit: {
maxRequests: 10,
perMilliseconds: 1000, // 10 requests per second
},
});
// Requests are automatically rate-limited
const users = await api.get('/users');Middleware Pattern
Add composable middleware for request processing:
const api = createNanoReq({
baseURL: 'https://api.example.com',
});
// Add authentication middleware
api.use(async config => {
const token = localStorage.getItem('token');
if (!token) {
return false; // Cancel request if no token
}
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
return config;
});
// Add logging middleware
api.use(async config => {
console.log(`Making ${config.method} request to ${config.url}`);
return config; // Continue with modified config
});
// Middleware runs before interceptors
const response = await api.get('/protected');TypeScript Usage
Full type safety with generics:
import { nanoReq } from 'nanoreq';
interface User {
id: number;
name: string;
email: string;
}
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
// Type-safe responses
const userResponse = await nanoReq.get<User>('https://api.example.com/users/1');
const user: User = userResponse.data;
const postsResponse = await nanoReq.get<Post[]>('https://api.example.com/posts');
const posts: Post[] = postsResponse.data;Comparison: NanoReq vs Axios vs Ky
A detailed comparison of popular HTTP client libraries:
| Feature | NanoReq | Axios | Ky | | ------------------------- | ---------------------------------------- | -------------------------- | -------------------------------- | | Bundle Size | ~4.6 KB (gzipped) | ~13 KB | ~3 KB | | Dependencies | 0 | Several | 0 | | HTTP Methods | ✅ GET, POST, PUT, PATCH, DELETE | ✅ All methods | ✅ GET, POST, PUT, PATCH, DELETE | | Interceptors | ✅ Request & Response | ✅ Request & Response | ❌ No | | Middleware | ✅ Composable middleware | ❌ No | ❌ No | | Rate Limiting | ✅ Built-in | ❌ No | ❌ No | | Request Deduplication | ✅ Automatic for GET | ❌ No | ❌ No | | Retry Logic | ✅ Configurable | ❌ No (plugin) | ✅ Built-in | | TypeScript | ✅ Full support | ✅ Full support | ✅ Full support | | Config Merging | ✅ Instance & request | ✅ Instance & request | ✅ Partial | | Base URL | ✅ | ✅ | ✅ | | Timeout | ✅ | ✅ | ✅ | | Request Cancellation | ✅ AbortSignal | ✅ CancelToken/AbortSignal | ✅ AbortSignal | | Progress Events | ❌ No | ✅ Yes | ❌ No | | Security Features | ✅ SSRF protection, header validation | ❌ No | ❌ No | | Request Pipeline | ✅ Unified pipeline architecture | ❌ No | ❌ No | | Debug Mode | ✅ Built-in logging | ❌ No | ❌ No | | FormData Support | ✅ Auto-detection | ✅ Yes | ✅ Yes | | Response Transform | ✅ Hooks | ✅ Transform response | ❌ No | | Error Types | ✅ NetworkError, TimeoutError, HTTPError | ✅ AxiosError | ✅ HTTPError | | Browser Support | Modern browsers (fetch API) | All browsers | Modern browsers (fetch API) | | Node.js Support | Node 18+ (native fetch) | All versions | Node 18+ (native fetch) |
When to Use Each Library
Choose NanoReq if:
- ✅ You need a lightweight Axios-like API
- ✅ You want built-in rate limiting and request deduplication
- ✅ You need security features for server-side usage
- ✅ You prefer a unified request pipeline architecture
- ✅ You want middleware support
- ✅ You need comprehensive TypeScript support
Choose Axios if:
- ✅ You need maximum compatibility (older browsers)
- ✅ You need progress events for uploads/downloads
- ✅ You have existing Axios code to migrate
- ✅ You need a battle-tested, widely-used library
Choose Ky if:
- ✅ You want the absolute smallest bundle size
- ✅ You only need basic HTTP requests
- ✅ You don't need interceptors or middleware
- ✅ You prefer a simpler, more minimal API
Browser Support
NanoReq works in all modern browsers that support the Fetch API:
- Chrome 42+
- Firefox 39+
- Safari 10.1+
- Edge 14+
For older browsers, you'll need a fetch polyfill.
Node.js Support
Requires Node.js 18+ (native fetch support) or use a fetch polyfill like node-fetch for older versions.
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
