| title | Fetch |
|---|---|
| description | Send HTTP requests with Bun's fetch API |
Bun implements the WHATWG fetch standard, with some extensions to meet the needs of server-side JavaScript.
Bun also implements node:http, but we generally recommend fetch instead.
To send an HTTP request, use fetch:
const response = await fetch("http://example.com");
console.log(response.status); // => 200
const text = await response.text(); // or response.json(), response.formData(), etc.fetch also works with HTTPS URLs.
const response = await fetch("https://example.com");You can also pass fetch a Request object.
const request = new Request("http://example.com", {
method: "POST",
body: "Hello, world!",
});
const response = await fetch(request);To send a POST request, pass an object with the method property set to "POST".
const response = await fetch("http://example.com", {
method: "POST",
body: "Hello, world!",
});body can be a string, a FormData object, an ArrayBuffer, a Blob, or another of the body types listed in the MDN documentation.
To proxy a request, pass an object with the proxy property set to a URL string, a URL instance, or to an object whose url is a string or a URL:
const response = await fetch("http://example.com", {
proxy: "http://proxy.com",
});To send custom headers to the proxy server, pass an object instead:
const response = await fetch("http://example.com", {
proxy: {
url: "http://proxy.com",
headers: {
"Proxy-Authorization": "Bearer my-token",
"X-Custom-Proxy-Header": "value",
},
},
});Bun sends the headers directly to the proxy in CONNECT requests (for HTTPS targets) or in the proxy request (for HTTP targets). If you provide a Proxy-Authorization header, it overrides any credentials in the proxy URL.
To set custom headers, pass an object with the headers property set to an object.
const response = await fetch("http://example.com", {
headers: {
"X-Custom-Header": "value",
},
});You can also set headers using the Headers object.
const headers = new Headers();
headers.append("X-Custom-Header", "value");
const response = await fetch("http://example.com", {
headers,
});To read the response body, use one of the following methods:
response.text(): Promise<string>: Returns a promise that resolves with the response body as a string.response.json(): Promise<any>: Returns a promise that resolves with the response body as a JSON object.response.formData(): Promise<FormData>: Returns a promise that resolves with the response body as aFormDataobject.response.bytes(): Promise<Uint8Array>: Returns a promise that resolves with the response body as aUint8Array.response.arrayBuffer(): Promise<ArrayBuffer>: Returns a promise that resolves with the response body as anArrayBuffer.response.blob(): Promise<Blob>: Returns a promise that resolves with the response body as aBlob.
You can use async iterators to stream the response body.
const response = await fetch("http://example.com");
for await (const chunk of response.body) {
console.log(chunk);
}You can also access the ReadableStream directly.
const response = await fetch("http://example.com");
const stream = response.body;
const reader = stream.getReader();
const { value, done } = await reader.read();You can also stream data in request bodies using a ReadableStream:
const stream = new ReadableStream({
start(controller) {
controller.enqueue("Hello");
controller.enqueue(" ");
controller.enqueue("World");
controller.close();
},
});
const response = await fetch("http://example.com", {
method: "POST",
body: stream,
});When using streams with HTTP(S):
- Bun streams the data directly to the network without buffering the entire body in memory
- If the connection is lost, Bun cancels the stream
- Bun sets the
Content-Lengthheader automatically only when the stream has a known size
When using streams with S3:
- For PUT/POST requests, Bun automatically uses multipart upload
- Bun consumes the stream in chunks and uploads the chunks in parallel
- You can monitor progress through the S3 options
To fetch a URL with a timeout, use AbortSignal.timeout:
const response = await fetch("http://example.com", {
signal: AbortSignal.timeout(1000),
});To cancel a request, use an AbortController:
const controller = new AbortController();
const response = await fetch("http://example.com", {
signal: controller.signal,
});
controller.abort();To fetch a URL using a Unix domain socket, use the unix: string option:
const response = await fetch("https://hostname/a/path", {
unix: "/var/run/path/to/unix.sock",
method: "POST",
body: JSON.stringify({ message: "Hello from Bun!" }),
headers: {
"Content-Type": "application/json",
},
});To use a client certificate, use the tls option:
await fetch("https://example.com", {
tls: {
key: Bun.file("/path/to/key.pem"),
cert: Bun.file("/path/to/cert.pem"),
// ca: [Bun.file("/path/to/ca.pem")],
},
});To customize TLS validation, use the checkServerIdentity option in tls:
await fetch("https://example.com", {
tls: {
checkServerIdentity: (hostname, peerCertificate) => {
// Return an Error if the certificate is invalid
},
},
});This option is similar to the one in Node's tls module.
To disable TLS validation, set rejectUnauthorized to false:
await fetch("https://example.com", {
tls: {
rejectUnauthorized: false,
},
});This avoids SSL errors with self-signed certificates, but it disables TLS validation, so use it with caution.
In addition to the standard fetch options, Bun provides several extensions:
const response = await fetch("http://example.com", {
// Control automatic response decompression (default: true)
// Supports gzip, deflate, brotli (br), and zstd
decompress: true,
// Disable connection reuse for this request
keepalive: false,
// Debug logging level
verbose: true, // or "curl" for more detailed output
});Beyond HTTP(S), Bun's fetch supports several additional protocols:
Bun supports fetching from S3 buckets directly.
// Using environment variables for credentials
const response = await fetch("s3://my-bucket/path/to/object");
// Or passing credentials explicitly
const response = await fetch("s3://my-bucket/path/to/object", {
s3: {
accessKeyId: "YOUR_ACCESS_KEY",
secretAccessKey: "YOUR_SECRET_KEY",
region: "us-east-1",
},
});Only PUT and POST methods support request bodies when using S3. For uploads, Bun automatically uses multipart upload for streaming bodies.
See the S3 documentation.
You can fetch local files using the file: protocol:
const response = await fetch("file:///path/to/file.txt");
const text = await response.text();On Windows, Bun normalizes paths automatically:
// Both work on Windows
const response = await fetch("file:///C:/path/to/file.txt");
const response2 = await fetch("file:///c:/path\\to/file.txt");Bun supports the data: URL scheme:
const response = await fetch("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
const text = await response.text(); // "Hello, World!"You can fetch blobs using URLs created by URL.createObjectURL():
const blob = new Blob(["Hello, World!"], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const response = await fetch(url);Bun's fetch implementation includes several specific error cases:
- Using a request body with GET/HEAD methods throws an error (which is expected for the fetch API)
- Using the
proxyandunixoptions together throws an error - TLS certificate validation failures when
rejectUnauthorizedis true (or undefined) - S3 operations may throw specific errors related to authentication or permissions
Bun automatically sets the Content-Type header for request bodies when not explicitly provided:
- For
Blobobjects, uses the blob'stype - For
FormData, sets appropriate multipart boundary
For debugging, pass verbose: true to fetch:
const response = await fetch("http://example.com", {
verbose: true,
});This prints the request and response headers to your terminal:
[fetch] > HTTP/1.1 GET http://example.com/
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br, zstd
[fetch] < 200 OK
[fetch] < Content-Encoding: gzip
[fetch] < Age: 201555
[fetch] < Cache-Control: max-age=604800
[fetch] < Content-Type: text/html; charset=UTF-8
[fetch] < Date: Sun, 21 Jul 2024 02:41:14 GMT
[fetch] < Etag: "3147526947+gzip"
[fetch] < Expires: Sun, 28 Jul 2024 02:41:14 GMT
[fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
[fetch] < Server: ECAcc (sac/254F)
[fetch] < Vary: Accept-Encoding
[fetch] < X-Cache: HIT
[fetch] < Content-Length: 648verbose: boolean is a Bun-specific extension, not part of the Web standard fetch API.
Before an HTTP request can be sent, Bun has to resolve DNS, connect the TCP socket, and sometimes complete a TLS handshake. Each step takes time, especially over a slow DNS server or a poor network connection. After the request completes, consuming the response body also takes time and memory.
Bun provides APIs to optimize each of these steps.
Use dns.prefetch when you know you'll connect to a host soon and want to avoid the initial DNS lookup.
import { dns } from "bun";
dns.prefetch("bun.com");By default, Bun caches and deduplicates DNS queries in-memory for up to 30 seconds. dns.getCacheStats() returns the cache stats.
See DNS caching.
fetch.preconnect starts the DNS lookup, TCP socket connection, and TLS handshake for a host before you're ready to send a request to it.
import { fetch } from "bun";
fetch.preconnect("https://bun.com");Calling fetch immediately after fetch.preconnect does not make your request faster. Preconnecting only helps when there's a gap between knowing the host and sending the request.
To preconnect to a host at startup, pass --fetch-preconnect:
bun --fetch-preconnect https://bun.com ./my-script.ts--fetch-preconnect is similar to <link rel="preconnect"> in HTML. It is not implemented on Windows; if you need it there, file an issue.
Bun automatically reuses connections to the same host. This is called connection pooling, and it can significantly reduce the time spent establishing connections.
By default, Bun limits the number of simultaneous fetch requests to 256, for two reasons:
- It improves overall system stability. Operating systems have an upper limit on the number of simultaneous open TCP sockets, usually in the low thousands. Nearing this limit causes your entire computer to behave strangely. Applications hang and crash.
- It encourages HTTP Keep-Alive connection reuse. For short-lived HTTP requests, the slowest step is often the initial connection setup. Reusing connections can save a lot of time.
When the limit is exceeded, Bun queues requests and sends them as soon as the next request ends.
To raise the limit, set the BUN_CONFIG_MAX_HTTP_REQUESTS environment variable:
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.tsThe max value for this limit is 65,535. The maximum port number is 65,535, so it's quite difficult for any one computer to exceed this limit.
The fastest way to read the response body is to use one of these methods:
response.text(): Promise<string>response.json(): Promise<any>response.formData(): Promise<FormData>response.bytes(): Promise<Uint8Array>response.arrayBuffer(): Promise<ArrayBuffer>response.blob(): Promise<Blob>
You can also use Bun.write to write the response body to a file on disk:
import { write } from "bun";
await write("output.txt", response);- Connection pooling is enabled by default. You can disable it per-request with
keepalive: falseor the"Connection: close"header. - Bun optimizes large file uploads using the operating system's
sendfilesyscall under specific conditions:- The file must be larger than 32KB
- The request must not be using a proxy or the
compressoption - On macOS, only regular files (not pipes, sockets, or devices) can use
sendfile - When these conditions aren't met, or when using S3/streaming uploads, Bun falls back to reading the file into memory
- Bun only uses this optimization for HTTP (not HTTPS) requests, where the file can be sent directly from the kernel to the network stack; Bun does not use it on Windows
- S3 operations automatically handle signing requests and merging authentication headers
Many of these features are Bun-specific extensions to the standard fetch API.