This page documents Bun's node:http and node:https compatibility modules: the classes they expose, how the module is assembled from internal submodules, and how the Node.js-style callback API maps to Bun's native HTTP stack.
For Bun's native HTTP server (Bun.serve), see page 5.1 For the native fetch() client, see page 5.2 For WebSocket support, see page 5.3 For node:stream compatibility, see page 8.6
node:http and node:https are compatibility modules that expose Node.js's HTTP callback-style API. They are implemented as a JavaScript layer in src/js/node/http.ts that assembles classes from several internal submodules. At runtime, these classes delegate to Bun's native HTTP engine (uWS/libusockets) and fetch() stack under the hood.
The node:http module in src/js/node/http.ts is a thin re-export layer. It imports from six internal private submodules and the internal/http utility module, then combines them into a single export object.
Module assembly diagram
Sources: src/js/node/http.ts1-71
The following symbols are exported from node:http:
| Export | Source submodule | Description |
|---|---|---|
Agent | node:_http_agent | Connection pool / keep-alive manager |
globalAgent | node:_http_agent | Default global Agent instance |
ClientRequest | node:_http_client | Outgoing client HTTP request |
IncomingMessage | node:_http_incoming | Incoming message (server requests, client responses) |
OutgoingMessage | node:_http_outgoing | Base class for outgoing messages |
Server | node:_http_server | HTTP server |
ServerResponse | node:_http_server | Server-side response object |
createServer | node:http (inline) | Factory: new Server(options, callback) |
request | node:http (inline) | Factory: new ClientRequest(url, options, cb) |
get | node:http (inline) | request() + immediate .end() |
METHODS | internal/http | Array of HTTP method strings |
STATUS_CODES | internal/http | Object mapping status codes to reason phrases |
maxHeaderSize | internal/http | Get/set max HTTP header size |
validateHeaderName | node:_http_common | Validates a header field name |
validateHeaderValue | node:_http_common | Validates a header field value |
setMaxIdleHTTPParsers | node:http (inline) | Sets parsers.max via validateInteger |
WebSocket | globalThis | Re-exported from global |
CloseEvent | globalThis | Re-exported from global |
MessageEvent | globalThis | Re-exported from global |
Sources: src/js/node/http.ts41-71
Class hierarchy diagram
Sources: test/js/node/http/node-http.test.ts212-227 test/js/node/http/node-http.test.ts982-1024 src/js/node/http.ts41-71
node:http.Server)Server is created via createServer(options?, callback?) or new Server(options, callback). The request callback receives (IncomingMessage, ServerResponse).
Listening options:
| Method | Behavior |
|---|---|
server.listen(port) | Listen on a TCP port; port: 0 assigns a random available port |
server.listen(path) | Listen on a Unix domain socket |
server.listen({ port, signal }) | Supports AbortSignal — when the signal fires, the server closes |
server.listen(undefined) | Assigns a random port |
server.listen() returns the server instance itself, so calls can be chained. The listen callback is bound to the server object (this === server).
server.address() returns { port, family, address } after binding.
Key behaviors:
req.connection.encrypted = false."get" → "GET").server.close() stops accepting new connections.Sources: test/js/node/http/node-http.test.ts49-210 test/js/node/http/node-http.test.ts1094-1122
ServerResponse extends OutgoingMessage. It is passed as the second argument to the server request callback.
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.write(chunk)
res.end(optionalData)
Header methods:
| Method | Behavior |
|---|---|
res.setHeader(name, value) | Sets a single header; value may be a string or array (for Set-Cookie) |
res.getHeader(name) | Returns the stored header value; case-insensitive lookup |
res.getHeaders() | Returns all stored headers as a plain object; keys are lowercased |
Set-Cookie array headers are preserved across setHeader/getHeader/getHeaders.
The writeHead method is overridable — middleware patterns that wrap writeHead work correctly.
Sources: test/js/node/http/node-http.test.ts212-227 test/js/node/http/node-http.test.ts773-782
ClientRequest is created by http.request(url, options?, callback?) or http.get(url, options?, callback?). The callback receives an IncomingMessage representing the response.
Constructor arguments:
url: string, URL, or options objectoptions: { host, hostname, port, path, method, headers, timeout, signal, ca }cb: response callbackKey methods:
| Method | Behavior |
|---|---|
req.write(data) | Write a body chunk; works with strings and Buffer |
req.end(data?) | Finalize and send the request |
req.setHeader(name, value) | Set a request header before sending |
req.getHeader(name) | Returns the header value or undefined if not set |
req.setTimeout(ms, cb) | Fires 'timeout' event after ms milliseconds of inactivity |
req.setSocketKeepAlive() | No-op (accepted without error for compatibility) |
Events:
| Event | When fired |
|---|---|
response | Response headers received |
socket | Socket assigned to the request |
timeout | Request has been idle beyond the configured timeout |
abort | Request was aborted via AbortSignal |
error | Network error |
AbortSignal support: Pass signal in options to cancel the in-flight request. The 'abort' event fires on the request object when the signal fires.
http.get() behavior: Internally calls request() and immediately calls .end() on the result. The host option is used verbatim as a DNS lookup target — URL delimiter characters (e.g., /, ?) in host are not re-parsed and result in an ENOTFOUND error.
Sources: test/js/node/http/node-http.test.ts394-866 test/js/node/http/node-http.test.ts915-980
IncomingMessage serves a dual purpose: it represents incoming requests on the server side and incoming responses on the client side.
Server-side properties:
| Property | Type | Description |
|---|---|---|
req.url | string | Path + query string (e.g., "/hello?world") |
req.method | string | Uppercase HTTP method |
req.headers | object | All headers, lowercased, inheriting from Object.prototype |
req.connection | Socket | Underlying socket; .encrypted indicates TLS |
Client-side response properties:
| Property | Type | Description |
|---|---|---|
res.statusCode | number | HTTP status code |
res.headers | object | Response headers, lowercased |
Streaming: IncomingMessage implements the Readable stream interface. Data events are 'data' and 'end'. Call res.setEncoding('utf8') to receive string chunks.
req.pipe(destination) works, including piping to a PassThrough stream for buffering.
Sources: test/js/node/http/node-http.test.ts229-370 test/js/node/http/node-http.test.ts762-771
Agent manages HTTP connection reuse and keep-alive. It is constructed with new Agent(options?).
| Property | Default | Description |
|---|---|---|
agent.protocol | 'http:' | Protocol for this agent |
agent.maxSockets | Infinity | Maximum concurrent sockets |
agent.keepAlive | false | Enable keep-alive by default |
globalAgent is the default shared Agent instance used when no explicit agent is provided to request().
Agent.apply({}) works in addition to new Agent() for legacy compatibility.
keepSocketAlive(req) is a no-op (accepted without error).
Sources: test/js/node/http/node-http.test.ts982-1024
node:https exposes a parallel API for TLS-encrypted HTTP. It uses the same internal classes but adds TLS-specific options.
Additional options for https.request():
| Option | Description |
|---|---|
ca | Custom CA certificate(s) for server verification |
cert | Client certificate |
key | Client private key |
rejectUnauthorized | Whether to reject self-signed certs (default: true) |
The https module is used identically to http except for the protocol and TLS options:
The raw response body is not decompressed by node:https — if the server sends Content-Encoding: gzip, the compressed bytes are delivered as-is to the IncomingMessage stream.
Sources: test/js/node/http/node-http.test.ts453-473 test/js/node/http/node-http.test.ts868-890 test/js/node/http/node-http.test.ts1124-1146
HTTP server request lifecycle
HTTP client request lifecycle
Sources: src/js/node/http.ts13-38 test/js/node/http/node-http.test.ts49-210 test/js/node/http/node-http.test.ts229-370
validateHeaderName(name) and validateHeaderValue(name, value) are exported for validating raw header fields. They throw TypeError on invalid input:
validateHeaderName("foo:") — throws (colon in name is invalid)validateHeaderValue("Foo", undefined) — throws (value must not be undefined)validateHeaderValue("Foo", "Bar\r") — throws (carriage return in value is invalid)maxHeaderSize is a get/set property backed by getMaxHTTPHeaderSize() / setMaxHTTPHeaderSize() from internal/http.
setMaxIdleHTTPParsers(max) updates parsers.max (from node:_http_common) after validating max is an integer ≥ 1.
Sources: src/js/node/http.ts51-68 test/js/node/http/node-http.test.ts1063-1073
| Behavior | Details |
|---|---|
| Header casing | All incoming headers (request and response) are lowercased |
| Method casing | Method strings are uppercased (e.g., "get" → "GET") |
| Redirects | ClientRequest does not follow redirects by default |
req.connection.encrypted | Set to false on plain HTTP connections |
setSocketKeepAlive | Accepted as a no-op on ClientRequest |
| Unix sockets | Supported via server.listen(socketPath) |
AbortSignal on server | server.listen({ signal }) — signal fires → server closes |
AbortSignal on client | request(url, { signal }) — signal fires → request aborted, 'abort' event emitted |
Set-Cookie arrays | Multi-value Set-Cookie headers are preserved as arrays through setHeader/getHeader |
| Body on GET/HEAD | GET and HEAD requests discard any req.write() data |
OPTIONS body | OPTIONS requests do deliver a response body |
Sources: test/js/node/http/node-http.test.ts66-80 test/js/node/http/node-http.test.ts512-521 test/js/node/http/node-http.test.ts709-760 test/js/node/http/node-http.test.ts892-913