An HTTP/1.1 client, written from scratch for Node.js
—
Complete WHATWG-compliant implementations of fetch, WebSocket, EventSource, and related Web APIs for Node.js environments.
WHATWG fetch API implementation with full Web Standards compliance and performance optimizations.
/**
* WHATWG fetch API implementation
* @param input - URL or Request object
* @param init - Request initialization options
* @returns Promise resolving to Response
*/
function fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
type RequestInfo = string | URL | Request;
interface RequestInit {
method?: string;
headers?: HeadersInit;
body?: BodyInit;
mode?: RequestMode;
credentials?: RequestCredentials;
cache?: RequestCache;
redirect?: RequestRedirect;
referrer?: string;
referrerPolicy?: ReferrerPolicy;
integrity?: string;
keepalive?: boolean;
signal?: AbortSignal;
window?: null;
duplex?: RequestDuplex;
}
type RequestMode = 'cors' | 'no-cors' | 'same-origin' | 'navigate';
type RequestCredentials = 'omit' | 'same-origin' | 'include';
type RequestCache = 'default' | 'no-store' | 'reload' | 'no-cache' | 'force-cache' | 'only-if-cached';
type RequestRedirect = 'follow' | 'error' | 'manual';
type RequestDuplex = 'half';Usage Examples:
import { fetch } from 'undici';
// Simple GET request
const response = await fetch('https://api.example.com/users');
const users = await response.json();
// POST request with JSON
const createResponse = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
});
// Request with abort signal
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://slow-api.example.com/data', {
signal: controller.signal
});
const data = await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was aborted');
}
}
// Custom request options
const response = await fetch('https://api.example.com/data', {
method: 'GET',
mode: 'cors',
credentials: 'include',
cache: 'no-cache',
redirect: 'follow'
});WHATWG Request API implementation for representing HTTP requests.
/**
* Represents an HTTP request
*/
class Request {
constructor(input: RequestInfo, init?: RequestInit);
readonly method: string;
readonly url: string;
readonly headers: Headers;
readonly body: ReadableStream | null;
readonly bodyUsed: boolean;
readonly mode: RequestMode;
readonly credentials: RequestCredentials;
readonly cache: RequestCache;
readonly redirect: RequestRedirect;
readonly referrer: string;
readonly referrerPolicy: ReferrerPolicy;
readonly integrity: string;
readonly keepalive: boolean;
readonly signal: AbortSignal;
readonly duplex: RequestDuplex;
clone(): Request;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
bytes(): Promise<Uint8Array>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
}Usage Examples:
import { Request, fetch } from 'undici';
// Create reusable request
const baseRequest = new Request('https://api.example.com/users', {
method: 'GET',
headers: { 'Authorization': 'Bearer token123' }
});
// Clone and modify requests
const userRequest = baseRequest.clone();
const postsRequest = new Request('https://api.example.com/posts', baseRequest);
// Use with fetch
const response = await fetch(userRequest);
const users = await response.json();WHATWG Response API implementation for representing HTTP responses.
/**
* Represents an HTTP response
*/
class Response {
constructor(body?: BodyInit, init?: ResponseInit);
readonly status: number;
readonly statusText: string;
readonly ok: boolean;
readonly redirected: boolean;
readonly type: ResponseType;
readonly url: string;
readonly headers: Headers;
readonly body: ReadableStream | null;
readonly bodyUsed: boolean;
static error(): Response;
static json(data: any, init?: ResponseInit): Response;
static redirect(url: string | URL, status?: number): Response;
clone(): Response;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
bytes(): Promise<Uint8Array>;
formData(): Promise<FormData>;
json(): Promise<any>;
text(): Promise<string>;
}
interface ResponseInit {
status?: number;
statusText?: string;
headers?: HeadersInit;
}
type ResponseType = 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect';Usage Examples:
import { Response } from 'undici';
// Create custom responses
const jsonResponse = Response.json({ message: 'Hello World' });
const redirectResponse = Response.redirect('https://example.com/new-url', 302);
const errorResponse = Response.error();
// Process response data
const response = await fetch('https://api.example.com/data');
if (response.ok) {
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
const data = await response.json();
console.log(data);
} else {
const text = await response.text();
console.log(text);
}
} else {
console.error(`Request failed: ${response.status} ${response.statusText}`);
}WHATWG WebSocket API implementation for real-time bidirectional communication.
/**
* WebSocket client implementation
*/
class WebSocket extends EventTarget {
constructor(url: string | URL, protocols?: string | string[], options?: WebSocketInit);
static readonly CONNECTING: 0;
static readonly OPEN: 1;
static readonly CLOSING: 2;
static readonly CLOSED: 3;
readonly url: string;
readonly readyState: number;
readonly extensions: string;
readonly protocol: string;
binaryType: BinaryType;
send(data: string | ArrayBuffer | ArrayBufferView | Blob): void;
close(code?: number, reason?: string): void;
// Event handlers
onopen: ((event: Event) => void) | null;
onmessage: ((event: MessageEvent) => void) | null;
onerror: ((event: ErrorEvent) => void) | null;
onclose: ((event: CloseEvent) => void) | null;
}
interface WebSocketInit {
protocols?: string[];
headers?: HeadersInit;
followRedirects?: boolean;
maxRedirects?: number;
maxPayload?: number;
skipUTF8Validation?: boolean;
dispatcher?: Dispatcher;
}
type BinaryType = 'blob' | 'arraybuffer';Usage Examples:
import { WebSocket } from 'undici';
// Basic WebSocket connection
const ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = (event) => {
console.log('WebSocket connected');
ws.send('Hello Server!');
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
ws.onerror = (event) => {
console.error('WebSocket error:', event);
};
ws.onclose = (event) => {
console.log(`WebSocket closed: ${event.code} ${event.reason}`);
};
// WebSocket with protocols and headers
const authWs = new WebSocket('wss://api.example.com/socket', ['chat', 'v1'], {
headers: {
'Authorization': 'Bearer token123'
},
maxPayload: 64 * 1024 // 64KB
});
// Send different data types
authWs.send('text message');
authWs.send(new ArrayBuffer(8));
authWs.send(new Uint8Array([1, 2, 3, 4]));
// Close with custom code and reason
authWs.close(1000, 'Normal closure');WebSocket event classes for handling connection lifecycle.
/**
* WebSocket close event
*/
class CloseEvent extends Event {
constructor(type: string, eventInitDict?: CloseEventInit);
readonly code: number;
readonly reason: string;
readonly wasClean: boolean;
}
interface CloseEventInit extends EventInit {
code?: number;
reason?: string;
wasClean?: boolean;
}
/**
* WebSocket error event
*/
class ErrorEvent extends Event {
constructor(type: string, eventInitDict?: ErrorEventInit);
readonly message: string;
readonly error: any;
}
interface ErrorEventInit extends EventInit {
message?: string;
error?: any;
}
/**
* WebSocket message event
*/
class MessageEvent extends Event {
constructor(type: string, eventInitDict?: MessageEventInit);
readonly data: any;
readonly origin: string;
readonly lastEventId: string;
readonly source: any;
readonly ports: MessagePort[];
}
interface MessageEventInit extends EventInit {
data?: any;
origin?: string;
lastEventId?: string;
source?: any;
ports?: MessagePort[];
}/**
* Send a ping frame to a WebSocket
* @param ws - WebSocket instance
* @param data - Optional ping data
* @returns Promise resolving when pong is received
*/
function ping(ws: WebSocket, data?: Buffer): Promise<void>;Usage Examples:
import { WebSocket, ping } from 'undici';
const ws = new WebSocket('wss://example.com/socket');
ws.onopen = async () => {
// Send ping and wait for pong
try {
await ping(ws, Buffer.from('ping-data'));
console.log('Ping successful');
} catch (error) {
console.error('Ping failed:', error);
}
};Streaming WebSocket implementation providing a simpler streams-based API.
/**
* Streaming WebSocket implementation
*/
class WebSocketStream {
constructor(url: string | URL, options?: WebSocketStreamInit);
readonly url: string;
readonly connection: Promise<WebSocketConnection>;
close(closeInfo?: WebSocketCloseInfo): void;
}
interface WebSocketStreamInit {
protocols?: string[];
signal?: AbortSignal;
}
interface WebSocketConnection {
readonly readable: ReadableStream;
readonly writable: WritableStream;
readonly extensions: string;
readonly protocol: string;
}
interface WebSocketCloseInfo {
code?: number;
reason?: string;
}Specialized error types for WebSocket operations.
/**
* WebSocket-specific error class
*/
class WebSocketError extends Error {
constructor(message: string, init?: WebSocketErrorInit);
readonly closeCode?: number;
readonly reason?: string;
}
interface WebSocketErrorInit {
closeCode?: number;
reason?: string;
}Usage Examples:
import { WebSocketStream, WebSocketError } from 'undici';
// Create a streaming WebSocket connection
const wsStream = new WebSocketStream('wss://api.example.com/stream');
try {
const connection = await wsStream.connection;
// Use readable stream
const reader = connection.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log('Received:', value);
}
// Use writable stream
const writer = connection.writable.getWriter();
await writer.write('Hello Stream!');
await writer.close();
} catch (error) {
if (error instanceof WebSocketError) {
console.error(`WebSocket error: ${error.message}, code: ${error.closeCode}`);
}
}WHATWG EventSource API implementation for server-sent events.
/**
* Server-sent events client
*/
class EventSource extends EventTarget {
constructor(url: string | URL, eventSourceInitDict?: EventSourceInit);
static readonly CONNECTING: 0;
static readonly OPEN: 1;
static readonly CLOSED: 2;
readonly url: string;
readonly readyState: number;
readonly withCredentials: boolean;
close(): void;
// Event handlers
onopen: ((event: Event) => void) | null;
onmessage: ((event: MessageEvent) => void) | null;
onerror: ((event: Event) => void) | null;
}
interface EventSourceInit {
withCredentials?: boolean;
headers?: HeadersInit;
dispatcher?: Dispatcher;
}Usage Examples:
import { EventSource } from 'undici';
// Basic EventSource connection
const eventSource = new EventSource('https://api.example.com/events');
eventSource.onopen = () => {
console.log('EventSource connected');
};
eventSource.onmessage = (event) => {
console.log('Received event:', JSON.parse(event.data));
};
eventSource.onerror = (event) => {
console.error('EventSource error:', event);
};
// EventSource with credentials and custom headers
const authEventSource = new EventSource('https://api.example.com/private-events', {
withCredentials: true,
headers: {
'Authorization': 'Bearer token123'
}
});
// Listen for custom event types
authEventSource.addEventListener('user-update', (event) => {
const userData = JSON.parse(event.data);
console.log('User updated:', userData);
});
// Close connection
setTimeout(() => {
eventSource.close();
}, 30000);type BodyInit =
| ArrayBuffer
| AsyncIterable<Uint8Array>
| Blob
| FormData
| Iterable<Uint8Array>
| NodeJS.ArrayBufferView
| URLSearchParams
| null
| string;
type HeadersInit =
| Headers
| Record<string, string | ReadonlyArray<string>>
| Iterable<readonly [string, string]>;
type ReferrerPolicy =
| ''
| 'no-referrer'
| 'no-referrer-when-downgrade'
| 'same-origin'
| 'origin'
| 'strict-origin'
| 'origin-when-cross-origin'
| 'strict-origin-when-cross-origin'
| 'unsafe-url';Install with Tessl CLI
npx tessl i tessl/npm-undici