Neutrx API Reference

Import

import neutrx, {
  HttpAdapter,
  NeutrxError,
  NeutrxHTTPError,
  NeutrxHeaders,
  isNeutrxError,
} from 'neutrx';

Client Creation

const api = neutrx.create({
  baseURL: 'https://api.example.com',
  timeout: 30_000,
  connectTimeout: 10_000,
});

The default export is callable:

await neutrx('https://api.example.com/health');
await neutrx({ url: 'https://api.example.com/health', method: 'GET' });

CommonJS:

const { default: neutrx, isNeutrxError } = require('neutrx');

Global defaults:

neutrx.defaults.baseURL = 'https://api.example.com';
neutrx.defaults.headers = { 'X-Service': 'billing' };

Methods

Every method funnels through request() and returns a Promise<NeutrxResponse<T>> (except the streaming/generator/utility helpers noted below). Generic T types response.data.

Verb methods

Method Purpose
request(config) The single funnel. Takes a full config object (url, method, data, …). All other verbs delegate to it.
get(url, config?) GET. No body. Use for reads; deduplicated and cacheable by default.
post(url, data, config?) POST. data becomes the body (JSON-encoded for plain objects). Not retried unless idempotencyKey is set.
put(url, data, config?) PUT. Full-resource replace. Retried by default (retryMethods).
patch(url, data, config?) PATCH. Partial update. Not retried unless idempotencyKey is set.
delete(url, config?) DELETE. Optional body via config.data.
head(url, config?) HEAD. Headers only, no body returned.
options(url, config?) OPTIONS. Preflight/capability probe.

Body-encoding helpers

Method Encoding
postForm / putForm / patchForm multipart/form-data. Node serializes plain objects to a multipart body; browser-like runtimes convert to FormData. Use for file uploads with fields.
postUrlEncoded / putUrlEncoded / patchUrlEncoded application/x-www-form-urlencoded. Serializes the object to a query-string body.

Streaming & large transfers

Method Purpose
upload(url, data, config?) Upload with onUploadProgress events; streams/buffers a body.
download(url, config?) Download with onDownloadProgress events; pairs with responseType: 'buffer' / 'stream'.
paginate(url, options?) Async generator yielding pages — for await (const page of api.paginate(...)). See Pagination.
sse(url, handlers?) Server-Sent Events stream; handlers receive parsed events.
ws(url, options?) Opens a WebSocket through the same client defaults/hooks. See WebSocket.

Utility & cache control

Method Purpose
getUri(config) Build the final URL without sending. See Utility Methods.
clearCache(pattern?) Remove all cached responses (or those matching pattern).
invalidateCache(pattern?) Remove cached entries whose key or final URL matches a string/regex.
deleteCacheEntry(configOrUrl) Remove the cache entry for one specific final URL.

Utility Methods

getUri(config) builds the final request URL without dispatching. It applies baseURL, allowAbsoluteUrls, params, and paramsSerializer, and it preserves existing query strings and hash fragments:

api.getUri({ url: '/users?active=true#team', params: { page: 2 } });
// "/users?active=true&page=2#team" or with baseURL, "https://api.example.com/users?active=true&page=2#team"

isNeutrxError(error) and neutrx.isNeutrxError(error) narrow errors to Neutrx’s branded error classes. isCancel(error) and neutrx.isCancel(error) detect the CancelToken migration bridge.

clearCache() removes all cached responses, invalidateCache(pattern) removes cached entries whose internal key or final URL matches a string or regular expression, and deleteCacheEntry(configOrUrl) removes the entry for a specific final URL.

postForm(), putForm(), and patchForm() are multipart form helpers. In Node, plain objects are serialized as multipart bodies by the Node HTTP adapter. In browser and browser-like runtimes, plain objects are converted to FormData where the platform provides it.

Request Config

Config merges in three layers: library neutrx.defaults → instance defaults (neutrx.create({...})) → per-request config. Per-request wins.

Target & URL

Field Description
url Request path or absolute URL. Joined onto baseURL unless absolute and allowed.
baseURL Prefix applied to relative url.
allowAbsoluteUrls When false, absolute-looking urls are still forced through baseURL. Default true.
method HTTP verb. Set automatically by the verb helpers.
params Query params object, appended to the URL.
paramsSerializer Custom function to encode params into a query string.

Headers & auth

Field Description
headers Plain object or NeutrxHeaders. Normalized to NeutrxHeaders at request start.
auth Basic auth { username, password }; sets the Authorization header.
idempotencyKey Value for the idempotency header; also makes POST/PATCH retryable.
idempotencyKeyHeader Header name for the key. Default Idempotency-Key.

Body & data transform

Field Description
data Request body. Plain objects are JSON-encoded by default.
parseJson / stringifyJson Override the JSON parse/serialize functions.
transformRequest Function(s) run over the body before sending.
transformResponse Function(s) run over the raw body after receiving, before schema.
schema Validate response data; replaces data or throws NeutrxValidationError. See Response Schema Validation.
validation Plugin-driven request/response validation (ValidationPlugin).

Timeouts & cancellation

Field Description
timeout Total request deadline in ms.
connectTimeout Connection-establishment deadline in ms.
signal AbortSignal to cancel the request. Preferred.
cancelToken Axios-style cancel bridge for migration; prefer signal.
transitional.clarifyTimeoutError When true, timeout errors use ETIMEDOUT instead of ECONNABORTED.

Response handling & limits

Field Description
responseType 'json' (default), 'text', 'buffer', 'stream', etc.
responseEncoding Text decoding charset for buffered text.
validateStatus Predicate deciding which status codes resolve vs throw.
throwHttpErrors When false, non-2xx responses resolve instead of throwing.
maxRedirects Max redirect hops to follow.
maxContentLength Max response body size in bytes.
maxBodyLength Max request body size in bytes.
decompress (Node only) false preserves compressed response bytes.

Transport & adapter

Field Description
adapter 'http', 'fetch', 'http2', HttpAdapter, or a custom NeutrxAdapter.
fetch Custom fetch implementation for the fetch adapter.
httpVersion 1 or 2. 2 routes through node:http2. See HTTP/2.
serviceDiscovery Resolver + strategy for relative URLs. See Service Discovery.
proxy Proxy settings (Node, HTTP/1.1).
tls TLS options (CA, cert, key, rejectUnauthorized).
lookup Custom DNS lookup function.
httpAgent / httpsAgent (Node) custom agents for connection pooling.
socketPath (Node only) Unix domain socket path.
beforeRedirect Hook run after Neutrx validates each redirect hop.
maxRate (Node only) bandwidth cap in bytes/s, or [upload, download]; 0 = uncapped.

Safety, resilience & observability

Field Description
security SSRF/host/HTTPS controls + rate limiting. See Security Config.
egressPolicy Outbound egress allowlist. See Egress Policy.
resilience Retry, circuit breaker, bulkhead. See Resilience Config.
performance Caching + request dedup. See Performance Config.
instrumentation Metrics/telemetry hooks.
onUploadProgress / onDownloadProgress Progress callbacks (ProgressEvent).

Progress events include loaded, total, percent, progress, bytes, rate, estimated, and upload or download.

import type { ProgressEvent } from 'neutrx';

function renderProgress(event: ProgressEvent) {
  const percent = event.percent === undefined ? 'unknown' : `${event.percent.toFixed(1)}%`;
  const eta = event.estimated === undefined ? 'unknown' : `${event.estimated.toFixed(1)}s`;
  console.log(`${percent} complete, +${event.bytes} bytes, ${event.rate} B/s, eta ${eta}`);
}

await api.get('/exports/monthly.csv', {
  responseType: 'buffer',
  onDownloadProgress: renderProgress,
});

bytes is the delta since the previous event for that request direction. rate is bytes per second from the previous event. estimated is only present when Neutrx knows total and has a positive rate. Node HTTP can measure buffered bodies, Node streams, buffered responses, and response streams as callers consume them. Fetch-based adapters depend on platform ReadableStream support. Browser FormData, opaque platform-managed request bodies, missing Content-Length, and runtimes without readable response streams may only produce a final event or omit total, percent, and estimated.

security.rateLimit limits request count over time. maxRate limits Node HTTP upload/download bandwidth over time; pass [uploadBytesPerSecond, downloadBytesPerSecond] and use 0 for an uncapped direction.

Adapters can be selected with adapter: 'http', adapter: 'fetch', adapter: 'http2', constants such as HttpAdapter, or a custom NeutrxAdapter function. Node uses HTTP by default; browser-like runtimes use fetch.

Axios-compatible migration options include allowAbsoluteUrls, beforeRedirect, decompress, responseEncoding, and transitional.clarifyTimeoutError. allowAbsoluteUrls: false forces absolute-looking request URLs through baseURL; beforeRedirect runs after Neutrx validates and prepares the next redirect hop; decompress: false preserves compressed bytes; responseEncoding controls buffered text decoding; transitional.clarifyTimeoutError: true switches timeout error codes from ECONNABORTED to ETIMEDOUT.

Neutrx-specific options include backend safety and resilience controls such as security, egressPolicy, resilience, performance, instrumentation, serviceDiscovery, tls, socketPath, maxRate, schema, validation, and idempotencyKey.

Custom adapters receive the fully prepared NeutrxRequestConfig and return a RawHttpResponse. Interceptors, retries, circuit breaker, cache, metrics, response parsing, and redirect policy stay in the client lifecycle outside the adapter.

Responses include request when the adapter can expose a safe transport reference: Node HTTP returns ClientRequest, fetch returns Request where possible.

Use createSecureAdapter() for custom adapters that should reject URL mutation and redirect responses outside Neutrx redirect policy.

idempotencyKey sets Idempotency-Key. It also allows retrying POST and PATCH when retry policy says the failure is retryable.

Response Schema Validation

schema validates parsed and transformed response data before a successful response is returned. Validators may be Zod-like safeParse, parse, validate, TypeBox-style Check/Errors, or function validators. Successful schemas can return parsed data, which replaces response.data; failures throw NeutrxValidationError with normalized issues.

const userSchema = {
  parse(value: unknown) {
    if (value && typeof value === 'object' && 'id' in value) {
      return value as { readonly id: string };
    }
    throw Object.assign(new Error('invalid user'), {
      issues: [{ path: ['id'], message: 'id is required' }],
    });
  },
};

const response = await api.get('/users/1', { schema: userSchema });
response.data.id;

await api.get('/users/1', { schema: false }); // disables a client default schema

Service Discovery

const api = neutrx.create({
  serviceDiscovery: {
    resolver: ['https://api-a.internal.example', 'https://api-b.internal.example'],
    strategy: 'round-robin',
  },
});

await api.get('/health');

Resolvers can be static arrays or async functions. Discovery applies to relative request URLs and the selected endpoint is exposed as config.serviceEndpoint for adapters, hooks, and telemetry.

Security Config

security: {
  profile: 'strict' | 'standard' | 'legacy',
  allowedHosts: ['api.example.com'],
  deniedHosts: ['*.blocked.example'],
  enforceHTTPS: true,
  enableSSRFProtection: true,
  blockPrivateIPs: true,
  blockMetadataIPs: true,
}

Egress Policy

egressPolicy: {
  mode: 'webhook-target',
  allowedProtocols: ['https'],
  allowedPorts: [443],
  requirePublicDns: true,
  blockCloudMetadata: true,
}

api.getEgressPolicy() returns safe policy audit data.

Resilience Config

resilience: {
  enableRetry: true,
  maxRetries: 3,
  retryStrategy: 'exponential',
  retryDelay: 250,
  maxRetryDelay: 5000,
  retryJitter: true,
  retryMethods: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'],
  retryBudget: {
    maxRetries: 100,
    windowMs: 60_000,
    scope: 'origin',
    namespace: 'billing-api',
    store: sharedRetryBudgetStore,
  },
  adaptiveConcurrency: { enabled: true, initialLimit: 10, maxLimit: 50 },
  enableCircuitBreaker: true,
  failureThreshold: 5,
  successThreshold: 2,
  circuitTimeout: 30_000,
  circuitBreakerStorage: {
    store: sharedCircuitStateStore,
    scope: 'origin',
    namespace: 'billing-api',
  },
}

Shared stores are interfaces only. Core stays zero-dependency; Redis or database-backed stores belong in optional packages or application code.

Performance Config

performance: {
  enableCaching: true,
  deduplicateRequests: true,
  deduplicateRequestKey: config => `${config.method}:${config.url}:${config.headers.get('X-Tenant-ID') ?? ''}`,
  deduplicateMethods: ['GET', 'HEAD'],
  deduplicateHeaders: ['accept', 'authorization', 'range'],
  cacheStrategy: 'swr',
  cacheTTL: 300_000,
  revalidateAfter: 60_000,
  cacheStaleMax: 1_500_000,
  cacheMaxSize: 500,
  respectCacheHeaders: true,
  onRevalidate: event => console.log(event.url, event.updated),
  cacheAdapter,
}

Cache strategies are max-age, swr, and network-first. SWR marks stale hits with response.cached = true and response.stale = true, returns them immediately, and refreshes the entry in the background. ttl and stale-while-revalidate remain compatibility aliases.

Request deduplication defaults to enabled for identical inflight GET and HEAD requests only. Set deduplicateRequests: false to disable it. Other methods require explicit deduplicateMethods opt-in and an application-safe custom key.

HTTP/2

Use httpVersion: 2 or adapter: 'http2' to send requests through Node’s node:http2 transport:

const api = neutrx.create({
  baseURL: 'https://api.example.com',
  httpVersion: 2,
  http2Options: {
    sessionTimeout: 60_000,
    maxSessions: 50,
    maxConcurrentStreams: 100,
  },
});

HTTP/2 sessions are reused by origin and compatible TLS settings. http2Options.sessionTimeout closes idle sessions, maxSessions bounds the shared session pool, and maxConcurrentStreams caps active streams per session alongside the server’s remote setting. getHttp2SessionStats() reports active streams, session count, closed/destroyed flags, and remote stream limits. GOAWAY closes the affected session so the next request opens a fresh one.

The HTTP/2 adapter preserves Neutrx redirect handling and supports buffered and stream upload/download progress when byte counts are available. It does not support proxies, Unix socketPath, custom HTTP agents, or maxRate, and it does not silently fall back to HTTP/1.1 when HTTP/2 negotiation fails. Select adapter: 'http' or httpVersion: 1 for HTTP/1.1 behavior.

WebSocket

const socket = await api.ws<{ type: string }>('/realtime', {
  headers: { Authorization: 'Bearer service-token' },
  reconnect: { attempts: 3, delay: 500, backoff: 'exponential', maxDelay: 10_000 },
  parseMessage: data => JSON.parse(String(data)) as { type: string },
  onMessage: message => console.log(message.type),
});

socket.send('hello');
socket.close();

api.ws() prepares a GET upgrade request through the same client defaults as HTTP calls: baseURL, params, default headers, basic auth, service discovery, plugin beforeRequest hooks, and request interceptors run before the connection is opened. http: and https: URLs are converted to ws: and wss: for the actual WebSocket target.

Node performs the upgrade directly and sends prepared headers, including Authorization, during the handshake. Browser builds use native WebSocket; browsers do not expose custom handshake headers, so header mutations are available to hooks/interceptors but cannot be sent by the platform constructor.

Reconnect is opt-in. Use reconnect: true for bounded exponential reconnect defaults, or pass { attempts, delay, backoff, maxDelay }. backoff may be fixed, linear, exponential, or a function that receives the one-based reconnect attempt.

Interceptors

const id = api.interceptors.request.use(
  config => config,
  undefined,
  {
    synchronous: true,
    runWhen: config => config.method === 'GET',
  }
);
api.interceptors.request.eject(id);
api.interceptors.request.clear();

api.interceptors.response.use(response => response, error => error);
api.interceptors.response.clear();

Plugins

Register with api.use(Plugin). Built-in plugins (zero added runtime dependency):

Plugin What it does
OAuth2Plugin Client-credentials OAuth2. Fetches a bearer token from tokenURL, caches it, refreshes ~30s before expiry, and injects Authorization: Bearer … on every request. Configure via api.configureOAuth2({...}).
GraphQLPlugin Adds api.gql(endpoint, query, variables?, options?)POSTs { query, variables, operationName }, unwraps data, and throws on a non-empty errors array.
MockPlugin In-process mocking. api.mock.enable().register(urlPatternOrRegex, { status, data, … }) short-circuits matching requests with a mock response (optional delay) without hitting the network. For tests/demos.
ValidationPlugin Request- and response-body validation through config.validation. See note below.
WebSocketPlugin Compatibility shim only; prefer api.ws(url, options) directly.
LogPlugin Structured success/error log entries to the logger set via api.setLogger(...).
OtelPlugin OpenTelemetry bridge (spans + context propagation) using a host-installed @opentelemetry/api.
TraceContextPlugin Dependency-free W3C Trace Context + B3 propagation headers.

See Plugins for full configuration and examples of each.

OAuth2Plugin refreshes lazily on the next request after the token nears expiry; token requests are sent with skipOAuth: true so they don’t recurse. Set skipOAuth: true on any request that must not carry the injected bearer token.

GraphQLPlugin errors are thrown as an Error carrying graphQLErrors (the response errors array) and any partial data. Successful results expose extensions when the server returns them.

MockPlugin matches string patterns by URL substring and RegExp patterns by test(); the first registered match wins. Call api.mock.disable() or api.mock.clear() to stop mocking.

Factory plugins (from neutrx/plugins or the Node entry):

  • createAwsSigV4Plugin(options) — AWS Signature V4 request signing (Node only). See Plugins → AWS SigV4.
  • createHarRecorder(options) — capture traffic as a HAR 1.2 log with secret redaction. See Plugins → HAR Recording.
  • createOtelPlugin(options) / createTraceContextPlugin(options) — configurable variants of the built-ins above.

ValidationPlugin reads config.validation.request before dispatch and config.validation.response after parsing. Use the first-class schema option for normal response validation; use the plugin when request-body validation or central plugin hooks are needed. Validators may be functions or schema-like objects with safeParse, parse, validate, or TypeBox-style Check/Errors. Failures throw NeutrxValidationError.

WebSocketPlugin is retained as a compatibility plugin; api.ws(url, options) is available directly on clients.

LogPlugin writes structured request success and error entries to any logger installed with api.setLogger(logger). Success URLs omit query strings; error entries use the redacted toStructuredError() representation.

OtelPlugin enables the built-in OpenTelemetry bridge through api.use(OtelPlugin) without adding a runtime dependency to Neutrx. It creates a client span, propagates that span’s context, records retry-attempt events, and attaches safe HTTP and Neutrx attributes.

TraceContextPlugin provides dependency-free W3C Trace Context and B3 propagation. The resolved identity is available on response.traceContext and typed errors.

Distributed State

A single StateAdapter<T> key/value backend can power cross-process rate-limit and circuit-breaker state. See Config Reference → Distributed State for the full guide.

  • StateAdapter<T> — generic contract: get / set(key, value, ttlMs?) / optional delete / keys / clear.
  • MemoryStateAdapter<T> — in-process reference impl (Map-backed, TTL-aware, single-process).
  • RedisStateAdapter<T> — distributed backend over a user-supplied ioredis / node-redis client (Node only, zero added dependency).
  • namespaceAdapter(adapter, prefix) — prefix keys so one backend hosts many logical namespaces.
  • circuitStoreFromAdapter(adapter) / rateLimitStoreFromAdapter(adapter) — bridge an adapter into resilience.circuitBreakerStorage.store and security.rateLimit.storage.store.
import Redis from 'ioredis';
import { RedisStateAdapter, circuitStoreFromAdapter, rateLimitStoreFromAdapter } from 'neutrx';

const shared = new RedisStateAdapter({ client: new Redis(process.env.REDIS_URL), keyPrefix: 'neutrx:' });
const api = neutrx.create({
  resilience: { circuitBreakerStorage: { store: circuitStoreFromAdapter(shared) } },
  security: { rateLimit: { enabled: true, storage: { store: rateLimitStoreFromAdapter(shared) } } },
});

Request Batching (DataLoader)

DataLoader<K, V> is an opt-in utility that coalesces many .load(key) calls in the same tick into one batch function call and memoizes per key. Nothing in the request pipeline uses it unless you wire it. See DataLoader.

import { DataLoader } from 'neutrx';

const users = new DataLoader<string, User>(async ids => {
  const { data } = await api.get('/users', { params: { ids: ids.join(',') } });
  return ids.map(id => data.find(u => u.id === id) ?? new Error(`no user ${id}`));
});

const [a, b] = await Promise.all([users.load('1'), users.load('2')]); // one HTTP call

Headers

Request and client configs accept either a plain header object or NeutrxHeaders. Neutrx converts both to an internal NeutrxHeaders instance at request start, before request hooks and interceptors run.

await api.get('/plain', {
  headers: { Authorization: 'Bearer secret' },
});

const headers = new NeutrxHeaders({ Authorization: 'Bearer secret' });
headers.setContentType('application/json');
headers.setAuthorization(false);
headers.setUserAgent('billing-service/1.0');
headers.normalize();
for (const [name, value] of headers) console.log(name, value);
headers.redactSensitive();

await api.get('/class', { headers });

Header names are case-insensitive. Calling set(name, false) stores a non-emitted sentinel that blocks automatic overwrites such as inferred Content-Type; calling set(name, null) deletes the header.

Errors

All Neutrx errors are branded:

try {
  await api.get('/missing');
} catch (error) {
  if (isNeutrxError(error)) {
    console.error(error.code, error.toJSON());
  }
}

HTTP failures throw NeutrxHTTPError subclasses unless validateStatus accepts the status or throwHttpErrors: false is set.

Typed errors expose a stable category, request and trace identity, retryability, and a redacted toJSON() representation. toStructuredError(error) safely normalizes non-Neutrx errors for structured logging.

Frequently asked questions

How do I create a Neutrx client instance?

Call neutrx.create(config) to get an instance with its own defaults — baseURL, headers, security profile, retries, caching, and timeouts. Per-request options override instance defaults, which override library defaults.

What HTTP methods does Neutrx support?

Neutrx supports get, post, put, patch, delete, head, and options, plus the callable form neutrx(url, config) and a low-level request(config) funnel that every verb routes through.

What does a Neutrx response contain?

A response exposes data (parsed body), status, statusText, headers, and the resolved config. Response parsing and size limits are enforced before the body is returned.

How do I validate response data with Neutrx?

Pass a schema validator in the request config so the parsed body is checked before the promise resolves. Validation failures raise a typed Neutrx error instead of returning unverified data.


Back to top

Released under the MIT License. © Neutrx contributors.