@iam4x/request
v1.9.0
Published
A lightweight, TypeScript-first HTTP request utility with built-in retry logic, query string handling, and automatic undefined value filtering
Maintainers
Readme
@iam4x/request
A lightweight, TypeScript-first HTTP request utility with built-in retry logic, query string handling, and automatic undefined value filtering.
Features
- 🚀 Simple API - Clean, intuitive interface for making HTTP requests
- 🔄 Built-in Retry Logic - Automatic retry mechanism for failed requests
- ⏱️ Timeout Support - Per-request timeouts via
AbortControllerwith retry-aware behaviour - 🔗 Query String Utilities - Parse and stringify query parameters with support for arrays and nested objects
- 🧹 Automatic Cleanup - Filters out undefined values from request bodies and params
- 📝 Opt-in Text Responses - Return raw text for CSV or plain-text endpoints while keeping JSON as the default
- 📦 TypeScript First - Full TypeScript support with comprehensive type definitions
- ⚡ Zero Dependencies - Uses native
fetchAPI, no external HTTP libraries required - 🎯 Tree Shakeable - Import only what you need
Installation
npm install @iam4x/request
# or
yarn add @iam4x/request
# or
pnpm add @iam4x/request
# or
bun add @iam4x/requestQuick Start
import { request } from '@iam4x/request';
// Simple GET request
const data = await request<{ id: number; name: string }>({
url: 'https://api.example.com/users',
});
// GET request with query parameters
const users = await request({
url: 'https://api.example.com/users',
params: {
page: 1,
limit: 10,
status: 'active',
},
});
// POST request with body
const newUser = await request({
url: 'https://api.example.com/users',
method: 'POST',
body: {
name: 'John Doe',
email: '[email protected]',
},
});
// Request with retry logic
const result = await request({
url: 'https://api.example.com/data',
retries: 3, // Will retry up to 3 times on failure
});API Reference
request<T>(req: Request): Promise<T>
Makes an HTTP request with the specified options.
Parameters
req.url(string, required) - The URL to make the request toreq.method(string, optional) - HTTP method (GET,HEAD,POST,PUT,DELETE). Defaults toGETreq.headers(Record<string, string>, optional) - Custom headers to include in the requestreq.params(RequestParams, optional) - Query string parameters (automatically converted to query string)req.body(RequestParams, optional) - Request body (automatically JSON stringified)req.responseType("json" | "text", optional) - Successful response parsing mode. Defaults to"json"req.retries(number, optional) - Number of retry attempts on failure. Defaults to0req.timeout(number, optional) - Request timeout in milliseconds. UsesAbortControllerinternally. If bothtimeoutandretriesare set, each retry gets its own fresh timeoutreq.proxy(string, optional) - Proxy URL to route the request through (runtime-dependent)req.keepalive(boolean, optional) - Forwarded tofetchwhen provided. In Bun, setfalseto disable connection reuse for the request
Returns
Promise<T> - The parsed JSON response by default
When responseType: "text" is set, the return type is Promise<string> and the response body is returned as-is.
Example
const response = await request<ApiResponse>({
url: 'https://api.example.com/users',
method: 'POST',
headers: {
Authorization: 'Bearer token123',
},
params: {
include: ['profile', 'settings'],
},
body: {
name: 'John Doe',
email: '[email protected]',
},
retries: 3,
});Text Responses
Use responseType: 'text' when the endpoint returns raw text such as CSV or plain text. This only changes how successful responses are parsed. Request bodies are still JSON-stringified as before.
const text = await request({
url: 'https://api.example.com/health',
responseType: 'text',
});
const csv = await request({
url: 'https://api.example.com/export.csv',
responseType: 'text',
});Advanced Usage
Custom Headers
const data = await request({
url: 'https://api.example.com/protected',
headers: {
Authorization: 'Bearer your-token',
'X-Custom-Header': 'value',
},
});Complex Query Parameters
const data = await request({
url: 'https://api.example.com/search',
params: {
q: 'search term',
filters: {
category: 'electronics',
price: { min: 100, max: 500 },
},
tags: ['new', 'featured'],
},
});Retry Logic
The retry mechanism will automatically retry failed requests:
// Retry up to 3 times on failure
const data = await request({
url: 'https://api.example.com/unstable-endpoint',
retries: 3,
});Timeout
Use timeout (in milliseconds) to abort a request that takes too long. A fresh AbortController is created for every attempt, so each retry gets its own independent timeout window:
import { request, RequestTimeoutError } from '@iam4x/request';
// Abort if the server doesn't respond within 5 seconds
const data = await request({
url: 'https://api.example.com/slow-endpoint',
timeout: 5000,
});
// Combine with retries — each attempt has its own 3-second window
try {
const data = await request({
url: 'https://api.example.com/slow-endpoint',
timeout: 3000,
retries: 2, // up to 3 total attempts
});
} catch (error) {
if (error instanceof RequestTimeoutError) {
console.error(`All attempts timed out after ${error.timeout}ms each`);
}
}Proxy
Route requests through a proxy by providing the proxy URL. Support depends on your runtime environment:
const data = await request({
url: 'https://api.example.com/data',
proxy: 'http://my-proxy.internal:8080',
});Keepalive
Bun enables connection reuse by default. Set keepalive: false to disable it for a single request, or omit the option to preserve the runtime default:
const data = await request({
url: 'https://api.example.com/data',
keepalive: false,
});Type Safety
The request function is fully typed. Specify your response type for full type safety:
interface User {
id: number;
name: string;
email: string;
}
const user = await request<User>({
url: 'https://api.example.com/users/1',
});
// user is typed as User
const csv = await request({
url: 'https://api.example.com/users/export.csv',
responseType: 'text',
});
// csv is typed as stringError Handling
The request function throws a RequestError for non-2xx HTTP responses and a RequestTimeoutError when the timeout is exceeded on every attempt:
import { request, RequestError, RequestTimeoutError } from '@iam4x/request';
try {
const data = await request({
url: 'https://api.example.com/users/999',
timeout: 5000,
});
} catch (error) {
if (error instanceof RequestError) {
console.error(`Request failed: ${error.status} ${error.statusText}`);
console.error('Response data:', error.response);
}
if (error instanceof RequestTimeoutError) {
console.error(`Timed out after ${error.timeout}ms`);
}
}RequestError Class
message(string) - Error messagestatus(number) - HTTP status codestatusText(string) - HTTP status textresponse(unknown) - Parsed response body (JSON or text)
RequestTimeoutError Class
message(string) - Error message ("Request timed out after {n}ms")timeout(number) - The timeout value that was exceeded, in milliseconds
RequestParams Type
The RequestParams type supports:
stringnumberbooleanstring[]number[]Record<string, any>(nested objects)Array<Record<string, any>>(arrays of objects)
Behavior
- Undefined Values: Automatically filtered out from
paramsandbody - Content-Type: Automatically set to
application/jsonfor requests with a body - Response Parsing: Successful responses are parsed as JSON by default; use
responseType: "text"for raw text or CSV payloads - Query String Encoding: Special characters are automatically URL-encoded
- Array Parameters: Arrays in query params are serialized as repeated keys (
?tags=js&tags=ts) - Nested Objects: Nested objects in query params use bracket notation (
?user[name]=John)
Exports
The package exports:
request- Main request functionRequestError- Error class for non-2xx HTTP responsesRequestTimeoutError- Error class thrown when a request exceeds its timeoutRequest- Type for request optionsRequestParams- Type for request parameters/body
Requirements
- TypeScript 5.8.3 or higher (peer dependency)
- A JavaScript runtime that supports the
fetchAPI (Node.js 18+, Bun, or modern browsers)
License
MIT
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
