SecureReq is a lightweight TypeScript utility for secure HTTP requests with strict TLS defaults, automatic http/1.1 to http/2 negotiation, streaming I/O, and typed response parsing.
- Class-first API that probes each origin with
http/1.1first, then upgrades future requests tohttp/2when appropriate. - Also exposes
SimpleSecureReq, a shared client instance for one-off requests without manual construction. - Automatic HTTP/2 probing is conservative: only safe body-less auto requests are retried from negotiation failure to
http/1.1. - Supports response compression with
zstd,gzip, anddeflate. - Supports optional redirect following with configurable redirect limits.
- Supports streaming uploads and streaming downloads.
- Defaults to TLSv1.3, Post Quantum Cryptography key exchange, a limited set of strongest ciphers, and a
User-Agentheader.
npm install @typescriptprime/securereqRequirements: Node.js >= 24
Create a client and reuse it per origin:
import { Readable } from 'node:stream'
import { SecureReq } from '@typescriptprime/securereq'
const client = new SecureReq()
// First request to an origin uses http/1.1 probing.
const first = await client.Request(new URL('https://api64.ipify.org?format=json'), {
ExpectedAs: 'JSON',
})
// Later safe requests to the same origin can probe and establish http/2 automatically.
const second = await client.Request(new URL('https://api64.ipify.org?format=json'), {
ExpectedAs: 'JSON',
})
console.log(first.Protocol) // 'http/1.1'
console.log(second.Protocol) // 'http/2' when available after the safe probe
// Follow redirects automatically
const redirected = await client.Request(new URL('https://example.com/old-path'), {
ExpectedAs: 'String',
FollowRedirects: true,
MaxRedirects: 5,
})
console.log(redirected.Body)
// Stream upload + stream download
const streamed = await client.Request(new URL('https://example.com/upload'), {
HttpMethod: 'POST',
Payload: Readable.from(['chunk-1', 'chunk-2']),
ExpectedAs: 'Stream',
})
for await (const chunk of streamed.Body) {
console.log(chunk)
}For quick one-off requests, you can use the exported shared client:
import { SimpleSecureReq } from '@typescriptprime/securereq'
const response = await SimpleSecureReq.Request(new URL('https://api64.ipify.org?format=json'), {
ExpectedAs: 'JSON',
})
console.log(response.Body)- Recommended entry point.
- Keeps per-origin capability state:
- first request is sent with
http/1.1 Accept-Encoding: zstd, gzip, deflate- later safe requests can probe
http/2, and capability updates only reflect observed protocol/compression evidence
- first request is sent with
Close()closes cached http/2 sessions.OriginCapabilityCacheLimitbounds remembered origin capability entries with LRU-style eviction.- Invalid constructor options fail fast during initialization.
- An exported shared
SecureReqinstance for simple or occasional requests. - Useful when you do not need to manage your own client lifecycle manually.
- Supports the same
.Request(),.GetOriginCapabilities(), and.Close()methods as a manually createdSecureReqinstance.
Url: URLβ Target URL (must be an instance ofURL).Options?: HTTPSRequestOptionsβ Optional configuration object.
Returns:
- If
ExpectedAsis specified,Promise<HTTPSResponse<T>> - If
ExpectedAsis omitted,Promise<HTTPSResponse<unknown>>
Throws:
TypeErrorwhenUrlis not aURLinstance.Erroron request failure or on failed response parsing (e.g., invalid JSON).
Fields:
TLS?: { IsHTTPSEnforced?: boolean, MinTLSVersion?: 'TLSv1.2'|'TLSv1.3', MaxTLSVersion?: 'TLSv1.2'|'TLSv1.3', Ciphers?: string[], KeyExchanges?: string[], RejectUnauthorized?: boolean }- Defaults:
IsHTTPSEnforced: true, both Min and Max set toTLSv1.3, a small secure cipher list and key exchange choices. - When
IsHTTPSEnforcedistrue, a non-https:URL will throw. KeyExchangesis forwarded to Node.js as the TLS supported groups / curve list. For strictTLSv1.2+ ECDSA servers, overly narrow values such as onlyX25519may fail; include a compatible certificate curve such asP-256when needed.
- Defaults:
HttpHeaders?: Record<string,string>β Custom headers. AUser-Agentheader is provided by default.HttpMethod?: 'GET'|'POST'|'PUT'|'DELETE'|'PATCH'|'HEAD'|'OPTIONS'Payload?: string | ArrayBuffer | Uint8Array | Readable | AsyncIterableExpectedAs?: 'JSON'|'String'|'ArrayBuffer'|'Stream'β How to parse the response body.- Omitting
ExpectedAskeeps the runtime extension heuristic (.json,.txt, fallbackArrayBuffer) but the body type is intentionallyunknown.
- Omitting
PreferredProtocol?: 'auto'|'http/1.1'|'http/2'|'http/3'http/3is currently a placeholder preference and uses the same TCP/TLS negotiation path ashttp/2until native HTTP/3 transport is added.
EnableCompression?: booleanβ Enables automaticAccept-Encodingnegotiation and transparent response decompression.FollowRedirects?: booleanβ Follows redirect responses with aLocationheader.MaxRedirects?: numberβ Maximum redirect hops whenFollowRedirectsis enabled. Default:5.TimeoutMs?: numberβ Aborts the request if headers or body transfer exceed the given number of milliseconds.Signal?: AbortSignalβ Cancels the request using a standard abort signal.
{ StatusCode: number, Headers: Record<string,string|string[]|undefined>, Body: T, Protocol: 'http/1.1'|'http/2', ContentEncoding: 'identity'|'zstd'|'gzip'|'deflate', DecodedBody: boolean }
Notes:
- If
ExpectedAsis omitted, a heuristic is still used at runtime:.jsonβJSON,.txtβString, otherwiseArrayBuffer. - Because omitted
ExpectedAsmay produce different runtime body shapes, the TypeScript return type isunknown. Prefer explicitExpectedAsin application code. - When
ExpectedAsisJSON, the body is parsed and an error is thrown if parsing fails. - When
ExpectedAsisStream, the body is returned as a Node.js readable stream. - Redirects are returned as-is by default. Set
FollowRedirects: trueto follow them. 301/302convertPOSTintoGET,303converts non-HEADmethods intoGET, and307/308preserve method and payload.- Redirects that require replaying a streaming payload are rejected instead of silently re-sending the stream.
- Strict TLS defaults lean on TLSv1.3 and a reduced cipher list to encourage secure transport out of the box.
- TLS options are forwarded to Node's HTTPS or http/2 TLS layer (
minVersion,maxVersion,ciphers,ecdhCurve). - When SecureReq performs an ALPN probe for HTTPS, the negotiated
TLSSocketis reused for the actualhttp/2orhttp/1.1request instead of opening a second TLS connection. - The library uses
zodfor runtime validation of options. - Compression negotiation is origin-scoped. Subdomains are tracked independently.
GetOriginCapabilities().PreferredProtocolis updated from actual observed transport, and automatic fallback only occurs for safe negotiation failures before request bytes are sent.GetOriginCapabilities().SupportedCompressionsis only narrowed when the response provided actual compression evidence.GetOriginCapabilities().PreferredProtocolreflects the currently usable transport (http/1.1orhttp/2), whileHTTP3Advertisedrecords whether the origin advertisedh3.- http/3 advertisement points are recorded from response headers, but Node.js built-in http/3 transport is not yet used.
- Build:
npm run build(usestsc -p sources/tsconfig.json) - Test:
npm test(usesava) - Lint:
npm run lint
Contributions, bug reports and PRs are welcome β please follow the repository's contribution guidelines.
This project is licensed under the Apache-2.0 License. See the LICENSE file for details.