typed-rest-client
typed-rest-client is a Node HTTP library from Microsoft with two levels. RestClient joins a base URL, serializes JSON bodies, parses JSON responses into TypeScript generic result shapes, and treats HTTP errors according to REST-oriented rules. HttpClient exposes status, headers, response streams, and raw bodies without throwing merely because a server returned 4xx or 5xx. Both can use pluggable Basic, Bearer, personal access token, and legacy NTLM handlers, plus proxies, client certificates, redirects, retries for read operations, and keep-alive agents.
A defensible compatibility choice for existing Microsoft-flavored Node tooling that needs PAT auth, proxies, certificates, and CommonJS. New services making normal HTTP calls should prefer native fetch, undici, or got, and nobody should choose it today solely for the deprecated NTLM handler.
Use it if
- You maintain a Node service or automation tool in the Microsoft and Azure ecosystem where personal access token handlers and corporate proxies are common
- You need one CommonJS-friendly client with JSON convenience plus direct access to Node IncomingMessage streams
- You need explicit proxy credentials, proxy bypass rules, custom certificate authorities, or mutual TLS from file paths
- You support Node 16 and want bundled declarations with a long-established async API rather than depending on newer native fetch behavior
- You are on current Node and only need ordinary JSON requests: native fetch or undici gives you standards-based requests, AbortSignal cancellation, and fewer legacy dependencies
- Your project is browser, edge, or strict ESM code: this package imports Node http, https, fs, Buffer, and streams, publishes CommonJS-style subpaths without an exports map, and cannot run as a portable Web API client
- You expect generic response types to validate untrusted JSON: RestClient simply JSON.parse()s the body and assigns it to T, so malformed object shapes survive until your code touches them
- You want uniform error behavior: HttpClient returns every HTTP status without throwing, RestClient resolves 404 as result: null, and RestClient rejects other 4xx and 5xx responses with extra untyped fields on Error
- You need NTLM because it appears in the feature list: the handler source marks NTLM deprecated, cites protocol security concerns, does not support proxy agents, and brings legacy DES and MD4 implementation dependencies
Setup reality
npm install typed-rest-client includes declarations and requires Node 16 or newer; there are no peer dependencies or native builds. Choose the layer before writing error handling. RestClient parses JSON, resolves 404 with a null result, and throws for other 4xx or 5xx responses. HttpClient treats HTTP status as data and only throws for transport failures, leaving you to read the body and inspect message.statusCode. The imports are CommonJS-oriented subpaths such as typed-rest-client/RestClient, /HttpClient, and /Handlers, with no package exports map or browser build. A user-agent string is the first constructor argument. Socket timeout defaults to three minutes unless set, redirects are enabled with a maximum of 50, and HTTPS-to-HTTP downgrade is blocked unless explicitly enabled. Retries are off by default and, when enabled, cover only OPTIONS, GET, DELETE, and HEAD for selected network errors or 502, 503, and 504 responses; writes never retry. Bearer and PAT handlers avoid forwarding authorization to a different host unless you opt into cross-origin authentication. Explicit proxy config and HTTP_PROXY, HTTPS_PROXY, or NO_PROXY are supported. Certificate paths are read synchronously when the client is constructed, and ignoreSslError disables certificate verification rather than fixing trust. If keepAlive is true, call dispose() or agents remain open. Finally, TypeScript generics are assertions, deserializeDates converts every parseable string through a broad JSON reviver, and NODE_DEBUG=http can expose sensitive headers in logs.
Patterns
Fetch a typed JSON resourceget-typed-json
import * as restm from 'typed-rest-client/RestClient';
interface User { id: number; name: string }
const client = new restm.RestClient('inventory-service', 'https://api.example.com');
const response = await client.get<User>('/users/42');
if (response.result) {
console.log(response.result.name);
}A 404 resolves with result null, and User is only a compile-time assertion; validate the parsed object at a trust boundary.
Create a JSON resourcepost-json
interface CreatedUser { id: number; name: string }
const response = await client.create<CreatedUser>('/users', {
name: 'Ada',
});
console.log(response.statusCode, response.result?.id);create sends POST with application/json; update uses PATCH and replace uses PUT with the same JSON serialization behavior.
Encode query parameters and custom headerssend-query-parameters
const response = await client.get<User[]>('/users', {
queryParameters: {
params: { page: 2, role: ['admin', 'editor'] },
options: { arrayFormat: 'repeat' },
},
additionalHeaders: { 'x-request-id': requestId },
});Query encoding is provided by qs; the generic result is still not checked against User[] at runtime.
Attach bearer authenticationsend-bearer-token
import * as restm from 'typed-rest-client/RestClient';
import * as handlers from 'typed-rest-client/Handlers';
const auth = new handlers.BearerCredentialHandler(process.env.API_TOKEN!);
const client = new restm.RestClient(
'deployment-tool',
'https://api.example.com',
[auth],
);The handler does not forward Authorization after a redirect to another host unless cross-origin authentication is explicitly enabled.
Authenticate with a Microsoft-style PATsend-personal-access-token
import * as handlers from 'typed-rest-client/Handlers';
const auth = new handlers.PersonalAccessTokenCredentialHandler(
process.env.AZURE_DEVOPS_PAT!,
);
const client = new restm.RestClient('build-tool', organizationUrl, [auth]);The handler sends Basic credentials built from PAT:<token>; keep the token server-side and leave cross-origin forwarding disabled.
Use HttpClient when every status is datainspect-raw-response
import * as httpm from 'typed-rest-client/HttpClient';
const client = new httpm.HttpClient('health-checker');
const response = await client.get('https://api.example.com/health');
const body = await response.readBody();
console.log(response.message.statusCode, body);HttpClient does not reject for 404 or 500; inspect statusCode before trusting or parsing the body.
Read RestClient error metadatahandle-rest-error
try {
await client.update('/users/42', { name: 'Grace' });
} catch (error) {
const failure = error as Error & {
statusCode?: number;
result?: unknown;
responseHeaders?: Record<string, unknown>;
};
console.error(failure.statusCode, failure.message);
}This catch does not handle 404 because RestClient resolves that status with a null result; other statuses above 299 reject.
Enable retries for read operationsretry-read-requests
const client = new httpm.HttpClient('catalog-reader', [], {
allowRetries: true,
maxRetries: 3,
socketTimeout: 10_000,
});
const response = await client.get('https://api.example.com/catalog');Retries cover OPTIONS, GET, DELETE, and HEAD for selected network errors or 502, 503, and 504; POST, PUT, and PATCH never retry.
Route requests through a proxyconfigure-http-proxy
const client = new httpm.HttpClient('corp-tool', [], {
proxy: {
proxyUrl: 'http://proxy.company.example:8080',
proxyUsername: process.env.PROXY_USER,
proxyPassword: process.env.PROXY_PASSWORD,
proxyBypassHosts: ['^localhost$', '\.company\.internal$'],
},
});Bypass entries become regular expressions; the client can also read HTTP_PROXY, HTTPS_PROXY, and NO_PROXY from the process environment.
Load a custom CA and client certificateconfigure-mutual-tls
const client = new httpm.HttpClient('mtls-client', [], {
cert: {
caFile: '/etc/my-app/ca.pem',
certFile: '/etc/my-app/client.pem',
keyFile: '/etc/my-app/client-key.pem',
passphrase: process.env.CLIENT_KEY_PASSPHRASE,
},
socketTimeout: 15_000,
});Certificate files are read synchronously at construction; do not replace this with ignoreSslError, which disables verification.
Upload a file streamupload-stream
import { createReadStream } from 'node:fs';
const stream = createReadStream('/tmp/artifact.zip');
const response = await client.uploadStream<{ id: string }>(
'PUT',
'/artifacts/latest',
stream,
{ additionalHeaders: { 'content-type': 'application/zip' } },
);RestClient still expects the response body to be JSON; use HttpClient and response.message.pipe() for a streamed download.
Close keep-alive agentsdispose-keepalive-client
const client = new httpm.HttpClient('batch-worker', [], {
keepAlive: true,
maxSockets: 20,
});
try {
await client.get('https://api.example.com/jobs');
} finally {
client.dispose();
}The source documentation requires dispose() when keepAlive is enabled; using the client after disposal throws.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| undici | npm | Use it for a modern Node HTTP client, standards-based fetch, pooling, streaming, and AbortSignal support without legacy auth handlers. |
| got | npm | Use it in ESM Node applications that want hooks, rich retry controls, pagination, streams, and polished error objects. |
| axios | npm | Use it when one promise API must work in browsers and Node and interceptors matter more than NTLM or low-level IncomingMessage access. |