Note — This page documents the Bun.serve
API. This API is heavily optimized and represents the recommended way to build HTTP servers in Bun. Existing Node.js projects may use Bun's nearly complete implementation of the Node.js http
and https
modules.
Send a request
Bun implements the Web fetch
API for making HTTP requests. The fetch
function is available in the global scope.
const response = await fetch("https://bun.sh/manifest.json");
const result = (await response.json()) as any;
console.log(result.icons[0].src);
// => "/logo-square.jpg"
Start a server
Start an HTTP server in Bun with Bun.serve
.
Bun.serve({
fetch(req) {
return new Response(`Bun!`);
},
});
The fetch
handler handles incoming requests. It receives a Request
object and returns a Response
or Promise<Response>
.
Bun.serve({
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response(`Home page!`);
if (url.pathname === "/blog") return new Response("Blog!");
return new Response(`404!`);
},
});
To configure which port and hostname the server will listen on:
Bun.serve({
port: 8080, // defaults to $PORT, then 3000
hostname: "mydomain.com", // defaults to "0.0.0.0"
fetch(req) {
return new Response(`404!`);
},
});
Error handling
To activate development mode, set development: true
. By default, development mode is enabled unless NODE_ENV
is production
.
Bun.serve({
development: true,
fetch(req) {
throw new Error("woops!");
},
});
In development mode, Bun will surface errors in-browser with a built-in error page.

To handle server-side errors, implement an error
handler. This function should return a Response
to served to the client when an error occurs. This response will supercede Bun's default error page in development
mode.
Bun.serve({
fetch(req) {
throw new Error("woops!");
},
error(error: Error) {
return new Response(`<pre>${error}\n${error.stack}</pre>`, {
headers: {
"Content-Type": "text/html",
},
});
},
});
Note — Full debugger support is planned.
The call to Bun.serve
returns a Server
object. To stop the server, call the .stop()
method.
const server = Bun.serve({
fetch() {
return new Response("Bun!");
},
});
server.stop();
TLS
Bun supports TLS out of the box, powered by OpenSSL. Enable TLS by passing in a value for keyFile
and certFile
; both are required to enable TLS. If needed, supply a passphrase
to decrypt the keyFile
.
Bun.serve({
fetch(req) {
return new Response("Hello!!!");
},
keyFile: "./key.pem", // path to TLS key
certFile: "./cert.pem", // path to TLS cert
passphrase: "super-secret", // optional passphrase
});
The root CA and Diffie-Helman parameters can be configured as well.
Bun.serve({
fetch(req) {
return new Response("Hello!!!");
},
keyFile: "./key.pem", // path to TLS key
certFile: "./cert.pem", // path to TLS cert
caFile: "./ca.pem", // path to root CA certificate
dhParamsFile: "./dhparams.pem", // Diffie Helman parameters
});
Hot reloading
Thus far, the examples on this page have used the explicit Bun.serve
API. Bun also supports an alternate syntax.
import {type Serve} from "bun";
export default {
fetch(req) {
return new Response(`Bun!`);
},
} satisfies Serve;
Instead of passing the server options into Bun.serve
, export it. This file can be executed as-is; when Bun runs a file with a default
export containing a fetch
handler, it passes it into Bun.serve
under the hood.
This syntax has one major advantage: it is hot-reloadable out of the box. When any source file is changed, Bun will reload the server with the updated code without restarting the process. This makes hot reloads nearly instantaneous. Use the --hot
flag when starting the server to enable hot reloading.
bun --hot server.ts
It's possible to configure hot reloading while using the explicit Bun.serve
API; for details refer to Runtime > Hot reloading.
Streaming files
To stream a file, return a Response
object with a BunFile
object as the body.
import { serve, file } from "bun";
serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});
⚡️ Speed — Bun automatically uses the sendfile(2)
system call when possible, enabling zero-copy file transfers in the kernel—the fastest way to send files.
[v0.3.0+] You can send part of a file using the slice(start, end)
method on the Bun.file
object. This automatically sets the Content-Range
and Content-Length
headers on the Response
object.
Bun.serve({
fetch(req) {
// parse `Range` header
const [start = 0, end = Infinity] = req.headers
.get("Range") // Range: bytes=0-100
.split("=") // ["Range: bytes", "0-100"]
.at(-1) // "0-100"
.split("-") // ["0", "100"]
.map(Number); // [0, 100]
// return a slice of the file
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});
Benchmarks
Below are Bun and Node.js implementations of a simple HTTP server that responds Bun!
to each incoming Request
.
Bun.serve({
fetch(req: Request) {
return new Response(`Bun!`);
},
port: 3000,
});
require("http")
.createServer((req, res) => res.end("Bun!"))
.listen(8080);
The Bun.serve
server can handle roughly 2.5x more requests per second than Node.js on Linux.
Runtime | Requests per second |
---|---|
Node 16 | ~64,000 |
Bun | ~160,000 |

Reference
See TypeScript definitions