A modern, feature-rich HTTP request client for Node.js with support for HTTPS, cookies, retry logic, interceptors, and more.
- Promise-based API with async/await support
- HTTPS support with certificate validation options
- Cookie management with automatic cookie jar
- Retry logic with exponential backoff
- Request/Response interceptors for middleware functionality
- Streaming support for large files
- Proxy support for HTTP/HTTPS proxies
- Compression handling (gzip, deflate, brotli)
- Redirect following with method change support
- Query string and form data helpers
- Timeout support with configurable delays
- Custom agents for connection pooling
- TypeScript definitions included
- Small dependency footprint (only http/https-proxy-agent and tough-cookie at runtime — config validation, LRU caches and multipart encoding are zero-dependency internals; see package.json)
npm install nklientFull documentation (guides, guard reference, error catalog, configuration
reference) is published at https://nitishagar.github.io/nklient/
(source in website/).
const nklient = require('nklient');
// Simple GET request
const response = await nklient.get('https://api.example.com/users').exec();
console.log(response.body);
// POST request with JSON
const newUser = await nklient.post('https://api.example.com/users')
.json({ name: 'John Doe', email: 'john@example.com' })
.exec();
// Using async/await
try {
const data = await nklient.get('https://api.example.com/data')
.headers('Authorization', 'Bearer token')
.query({ page: 1, limit: 10 })
.exec();
console.log(data.body);
} catch (error) {
console.error('Request failed:', error.message);
}All HTTP methods return a RequestWrapper instance that can be configured with chainable methods.
nklient.get(url)
nklient.post(url)
nklient.put(url)
nklient.patch(url)
nklient.delete(url)
nklient.head(url)
nklient.options(url)// Set individual header
nklient.get(url)
.headers('Authorization', 'Bearer token')
.headers('X-Custom', 'value')
// Set multiple headers
nklient.get(url)
.headers({
'Authorization': 'Bearer token',
'X-Custom': 'value'
})// JSON body (auto-sets Content-Type)
nklient.post(url).json({ key: 'value' })
// Form data
nklient.post(url).form({ username: 'john', password: 'secret' })
// Raw body
nklient.post(url).body('raw string data')
nklient.post(url).body(Buffer.from('binary data'))nklient.get(url).query({ page: 1, limit: 10 })
// Results in: url?page=1&limit=10nklient.get(url).timeout(5000) // 5 seconds// Use custom cookie jar
const jar = nklient.jar();
nklient.get(url).jar(jar)
// Disable cookies for a request
nklient.get(url).noJar()nklient.get(url).retry({
attempts: 3,
delay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
retryOnStatusCodes: [408, 429, 500, 502, 503, 504]
})nklient.get(url)
.maxRedirects(5) // Maximum number of redirects
.encoding('utf8') // Response encoding (null for Buffer)
.stream() // Get response as stream
.rejectUnauthorized(false) // Disable SSL certificate validation
.proxy('http://proxy.example.com:8080') // Use proxy
.agent(customAgent) // Use custom HTTP agent{
statusCode: 200,
headers: {
'content-type': 'application/json',
// ... other headers
},
body: { /* parsed JSON or string/Buffer */ },
request: {
uri: 'https://example.com/api',
method: 'GET',
headers: { /* request headers */ }
}
}Add middleware to requests and responses:
// Request interceptor
const requestId = nklient.interceptors.request.use(async (config) => {
config.headers['X-Request-ID'] = generateId();
return config;
});
// Response interceptor
const responseId = nklient.interceptors.response.use(async (response) => {
console.log(`Response: ${response.statusCode}`);
return response;
});
// Remove interceptor
nklient.interceptors.request.eject(requestId);
nklient.interceptors.response.eject(responseId);Create instances with custom defaults:
const api = nklient.create({
headers: {
'Authorization': 'Bearer token',
'Content-Type': 'application/json'
},
timeout: 10000,
retry: {
attempts: 5,
delay: 1000
}
});
// Use instance
const response = await api.get('/users').exec();No global nklient.defaults() API exists — use create() / createClient() with per-instance config (see Custom Instances above).
const fs = require('fs');
const response = await nklient.post('https://api.example.com/upload')
.headers('Content-Type', 'application/octet-stream')
.body(fs.createReadStream('large-file.zip'))
.exec();const fs = require('fs');
const response = await nklient.get('https://example.com/large-file.zip')
.stream()
.exec();
const fileStream = fs.createWriteStream('downloaded-file.zip');
let downloaded = 0;
response.body.on('data', (chunk) => {
downloaded += chunk.length;
console.log(`Downloaded: ${downloaded} bytes`);
});
response.body.pipe(fileStream);try {
const response = await nklient.get('https://flaky-api.example.com/data')
.retry({
attempts: 3,
delay: 1000,
backoffMultiplier: 2,
retryOnStatusCodes: [408, 429, 500, 502, 503, 504]
})
.timeout(5000)
.exec();
console.log('Success:', response.body);
} catch (error) {
if (error.code === 'ETIMEDOUT') {
console.error('Request timed out');
} else if (error.code === 'ECONNREFUSED') {
console.error('Connection refused');
} else {
console.error('Request failed:', error.message);
}
}const response = await nklient.get('https://api.example.com/data')
.proxy('http://proxy.company.com:8080')
.exec();const jar = nklient.jar();
// Login request - cookies are saved
await nklient.post('https://api.example.com/login')
.jar(jar)
.json({ username: 'user', password: 'pass' })
.exec();
// Subsequent requests use saved cookies
const profile = await nklient.get('https://api.example.com/profile')
.jar(jar)
.exec();# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch
# Lint code
npm run lint
# Format code
npm run formatAll core features are implemented and working:
- ✅ Retry Logic - Exponential backoff with configurable retry conditions
- ✅ Cookie Handling - Automatic cookie management with tough-cookie
- ✅ Request Cancellation - AbortController support for request cancellation
- ✅ Streaming Support - Request and response streaming with progress tracking
- ✅ Proxy Support - HTTP/HTTPS proxy support with proper agent handling
Current Status: 224 passing, 0 pending, 0 failing — 15 end-to-end tests against live local servers (tests/e2e.test.js, zero mocks) plus the full mocked suite.
Coverage (c8 on dist/): see thoughts/shared/plans/nklient-full-feature-set/TEST_VALIDATION_S4.md for the final gate table (gates: 90 lines / 85 funcs / 80 branches).
Redirect-limit and oversize-body rejections throw typed errors (assert err.code, not message text):
MaxRedirectsError(code: 'ERR_MAX_REDIRECTS') on redirect-limit breach.ResponseTooLargeError(code: 'ERR_RESPONSE_TOO_LARGE') onmaxResponseSizebreach (stream destroyed mid-body).
Negative numeric inputs throw RangeError at the setter seam (mirrors createClient schema minima of 0); zero has pinned per-field meaning:
| Input | 0 |
Negative |
|---|---|---|
timeout (scalar / object value) |
no timeout (disabled) | throw RangeError |
maxRedirects |
no follows (first redirect rejects MaxRedirectsError) |
throw RangeError |
maxResponseSize |
no limit (same as unset) | throw RangeError |
retry.attempts |
0/1 = single attempt (no RetryExhaustedError) |
throw RangeError |
retry.delay |
no delay (immediate retry) | throw RangeError |
Abort wins during retry backoff: aborting mid-backoff rejects AbortError (ERR_ABORTED) promptly instead of sleeping through the full delay (no further attempt fires).
Secure by default; guards apply to redirect targets only (same-hostname hops exempt from the private-network guard):
allowHttpsToHttp(defaultfalse) — blockshttps:→http:downgrades unless opted in (ProtocolDowngradeError,PROTOCOL_DOWNGRADE).blockPrivateNetworks(defaulttrue) — blocks cross-host pivots into private address space unless opted out (PrivateNetworkError,PRIVATE_NETWORK).allowedDomains/blockedDomains(default[]= no filtering) — subdomain-aware allowlist/blocklist; blocklist wins (UnauthorizedDomainError,UNAUTHORIZED_DOMAIN/BlockedDomainError,BLOCKED_DOMAIN).- Redirect cycles fail fast (
RedirectLoopError,REDIRECT_LOOP). - TLS 1.2 minimum by default on HTTPS (
minVersion: 'TLSv1.2'on the global agent and per request);rejectUnauthorized(false)only warns (never throws). - Responses carry a
dataalias (same reference asbody).
Passing Test Areas:
- Basic HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
- HTTPS support and certificate validation
- Headers, query parameters, and request body handling
- Cookie management and retry logic
- Interceptors and streaming support
- Custom agents and createClient functionality
Apache License 2.0