http-call
http-call is a small CommonJS HTTP client for Node.js built over the core http and https modules. It offers static methods for GET, POST, PUT, PATCH, DELETE, arbitrary requests, and response streaming; parses JSON according to the response Content-Type; serializes object request bodies as JSON; follows redirects; retries selected network errors; and honors common proxy environment variables. The returned object exposes the parsed body, status, headers, request, response, and final URL rather than imitating the browser fetch API.
Do not start new code with the unscoped package: it is frozen at 5.3.0 while maintenance continues under @heroku/http-call. Keep it only for compatible legacy code, then plan a move to the scoped package or a current client with explicit retry and redirect policy.
Use it if
- You maintain older Heroku CLI code that already imports the unscoped package and depends on its HTTP, HTTPError, or Next-Range behavior
- You want a CommonJS Node client that automatically parses JSON and turns non-2xx responses into an error carrying statusCode and body
- You need HTTP_PROXY, HTTPS_PROXY, NO_PROXY, SSL_CERT_FILE, and SSL_CERT_DIR support without constructing a proxy agent yourself
- You consume an API that uses Heroku-style Next-Range headers and want array pages concatenated automatically
- You are choosing a client for new code: Node's built-in fetch covers ordinary requests without installing a package, while undici and got have broader current documentation and controls
- You expect the npm name to receive current fixes: unscoped http-call stops at 5.3.0 from December 2019, while the active repository now names and releases the package as @heroku/http-call 5.6.0
- Requests may redirect across origins while carrying credentials: 5.3.0 reuses its headers on redirects, while the repository's 5.6.0 release added removal of authorization, cookie, proxy-authorization, and x-addon-sso on cross-origin redirects
- You need explicit retry policy or safe treatment of writes: 5.3.0 retries eligible network failures up to five times for every method, including POST, and its public options provide no retry switch
- You download large non-streamed responses or large paginated arrays: normal requests concatenate the full body in memory, automatic Next-Range paging concatenates every page, and 5.3.0 exposes no response-size cap
Setup reality
Installing the requested package is only `npm install http-call`. Version 5.3.0 has no peer dependency, native addon, credential file, or build step for consumers; it ships compiled CommonJS and its own TypeScript declarations, and its package metadata allows Node 8 or newer. The larger surprise is package identity. The active GitHub repository now has `@heroku/http-call` at 5.6.0, while `http-call` on npm still resolves to 5.3.0 from 2019. That old build has six runtime dependencies and misses newer fixes, including a default timeout, optional redirect following, safer relative redirect resolution, empty JSON-body handling, and removal of sensitive headers on cross-origin redirects. In 5.3.0 there is no default timeout, no AbortSignal option, no user-facing retry option, and no redirect-disable option. Selected DNS and socket errors are retried as many as five times with backoff, even for write methods, so callers must consider duplicate side effects. Object bodies become JSON only when no Content-Type is supplied or it is exactly application/json; form data must be encoded before passing it. Successful JSON is parsed only when the server sends application/json or a +json media type. Every non-2xx response becomes HTTPError, but its body is read into memory first. Proxy discovery is automatic from HTTP_PROXY, HTTPS_PROXY, and NO_PROXY. Corporate certificate files are read synchronously from SSL_CERT_FILE and every file in SSL_CERT_DIR when the proxy agent is created. Debug output uses the `http` and `http:headers` namespaces; authorization is redacted unless HTTP_CALL_REDACT=0, but other secret headers in 5.3.0 are not covered by that redaction rule.
Patterns
Read a JSON responseget-json
const { HTTP } = require('http-call');
const { body, statusCode, headers } = await HTTP.get(
'https://api.example.com/users/42'
);
console.log(statusCode, headers['content-type'], body);The body becomes an object only when the server declares application/json or a media type ending in +json; otherwise it remains a string.
Type the response body in TypeScripttype-json-body
import { HTTP } from 'http-call';
type User = { id: string; email: string };
const { body } = await HTTP.get<User>(
'https://api.example.com/users/42'
);
console.log(body.email);The generic is a compile-time assertion, not runtime validation. A malformed or differently shaped JSON response still reaches your code.
Build a query string explicitlysend-query-parameters
const query = new URLSearchParams({
state: 'open',
limit: '25',
});
const { body } = await HTTP.get(
`https://api.example.com/issues?${query}`
);Version 5.3.0 has no params option or serializer; query parameters must already be present in the URL.
Send authorization and custom headerssend-auth-header
const { body } = await HTTP.get('https://api.example.com/me', {
headers: {
authorization: `Bearer ${process.env.API_TOKEN}`,
accept: 'application/json',
},
});Version 5.3.0 follows redirects and reuses these headers, including across origins. Do not call redirecting URLs with secrets unless you control the complete redirect chain.
POST an object as JSONpost-json
const { body } = await HTTP.post('https://api.example.com/users', {
body: {
name: 'Ada',
email: 'ada@example.com',
},
});
console.log(body);A truthy object body is stringified, Content-Type defaults to application/json, and Content-Length is set. Empty strings, 0, and false are treated as no body.
POST URL-encoded form datapost-form
const form = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'read:orders',
}).toString();
const { body } = await HTTP.post('https://api.example.com/token', {
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: form,
});Encode the form yourself. When Content-Type is not exactly application/json, the package sends the supplied string without JSON serialization.
Use PUT, PATCH, and DELETEuse-write-methods
await HTTP.put('https://api.example.com/users/42', {
body: { name: 'Ada Lovelace' },
});
await HTTP.patch('https://api.example.com/users/42', {
body: { active: false },
});
await HTTP.delete('https://api.example.com/users/42');Eligible network failures are retried for all methods in 5.3.0. A write can be repeated if the server acted before the connection failed.
Create a client with shared defaultscreate-configured-client
const API = HTTP.create({
host: 'api.example.com',
protocol: 'https:',
timeout: 10000,
headers: { accept: 'application/json' },
});
const { body } = await API.get('/v1/orders');HTTP.create returns a subclass with defaults. Pass paths beginning with /; a full URL can override the configured host and protocol.
Set a request timeoutset-timeout
const { body } = await HTTP.get('https://api.example.com/slow', {
timeout: 10000,
});The unscoped 5.3.0 build has no default timeout and no AbortSignal option. Set a timeout on every call or in an HTTP.create client.
Inspect non-2xx failureshandle-http-errors
const { HTTP, HTTPError } = require('http-call');
try {
await HTTP.get('https://api.example.com/private');
} catch (error) {
if (error instanceof HTTPError) {
console.error(error.statusCode, error.body);
} else {
throw error;
}
}HTTPError is used for responses outside 200 through 299. DNS, socket, timeout, parse, and redirect failures are different errors.
Stream a successful response to diskstream-download
const { createWriteStream } = require('node:fs');
const { pipeline } = require('node:stream/promises');
const { response } = await HTTP.stream(
'https://downloads.example.com/archive.zip'
);
await pipeline(response, createWriteStream('archive.zip'));Raw streaming applies only to successful responses. Error responses are buffered and parsed before HTTPError is thrown.
Choose automatic or single-page Next-Range handlingcontrol-next-range-paging
// Concatenates array pages while the server returns Next-Range.
const all = await HTTP.get('https://api.example.com/events');
// Stops after the first response.
const first = await HTTP.get('https://api.example.com/events', {
partial: true,
});
console.log(all.body, first.body, first.headers['next-range']);Automatic paging works only for GET responses whose parsed body is an array. It buffers and concatenates every page, so partial: true is safer for large collections.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @heroku/http-call | npm | You need this API but want the actively released scoped package with current redirect and timeout fixes |
| undici | npm | You want the Node fetch implementation plus pooling, dispatchers, and lower-level HTTP controls |
| got | npm | You want a current Node-only client with documented retry, hooks, pagination, streaming, and cancellation controls |
| axios | npm | You need one familiar client across Node and browsers, especially for interceptors and request configuration |