UNPKG

bun-types

Version:

Type definitions and documentation for Bun, an incredibly fast JavaScript runtime

1,345 lines (1,041 loc) 34.4 kB
The page primarily documents the Bun-native `Bun.serve` API. Bun also implements [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and the Node.js [`http`](https://nodejs.org/api/http.html) and [`https`](https://nodejs.org/api/https.html) modules. {% callout %} These modules have been re-implemented to use Bun's fast internal HTTP infrastructure. Feel free to use these modules directly; frameworks like [Express](https://expressjs.com/) that depend on these modules should work out of the box. For granular compatibility information, see [Runtime > Node.js APIs](https://bun.sh/docs/runtime/nodejs-apis). {% /callout %} To start a high-performance HTTP server with a clean API, the recommended approach is [`Bun.serve`](#start-a-server-bun-serve). ## `Bun.serve()` Use `Bun.serve` to start an HTTP server in Bun. ```ts Bun.serve({ // `routes` requires Bun v1.2.3+ routes: { // Static routes "/api/status": new Response("OK"), // Dynamic routes "/users/:id": req => { return new Response(`Hello User ${req.params.id}!`); }, // Per-HTTP method handlers "/api/posts": { GET: () => new Response("List posts"), POST: async req => { const body = await req.json(); return Response.json({ created: true, ...body }); }, }, // Wildcard route for all routes that start with "/api/" and aren't otherwise matched "/api/*": Response.json({ message: "Not found" }, { status: 404 }), // Redirect from /blog/hello to /blog/hello/world "/blog/hello": Response.redirect("/blog/hello/world"), // Serve a file by buffering it in memory "/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), { headers: { "Content-Type": "image/x-icon", }, }), }, // (optional) fallback for unmatched routes: // Required if Bun's version < 1.2.3 fetch(req) { return new Response("Not Found", { status: 404 }); }, }); ``` ### Routing Routes in `Bun.serve()` receive a `BunRequest` (which extends [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)) and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or `Promise<Response>`. This makes it easier to use the same code for both sending & receiving HTTP requests. ```ts // Simplified for brevity interface BunRequest<T extends string> extends Request { params: Record<T, string>; readonly cookies: CookieMap; } ``` #### Async/await in routes You can use async/await in route handlers to return a `Promise<Response>`. ```ts import { sql, serve } from "bun"; serve({ port: 3001, routes: { "/api/version": async () => { const [version] = await sql`SELECT version()`; return Response.json(version); }, }, }); ``` #### Promise in routes You can also return a `Promise<Response>` from a route handler. ```ts import { sql, serve } from "bun"; serve({ routes: { "/api/version": () => { return new Promise(resolve => { setTimeout(async () => { const [version] = await sql`SELECT version()`; resolve(Response.json(version)); }, 100); }); }, }, }); ``` #### Type-safe route parameters TypeScript parses route parameters when passed as a string literal, so that your editor will show autocomplete when accessing `request.params`. ```ts import type { BunRequest } from "bun"; Bun.serve({ routes: { // TypeScript knows the shape of params when passed as a string literal "/orgs/:orgId/repos/:repoId": req => { const { orgId, repoId } = req.params; return Response.json({ orgId, repoId }); }, "/orgs/:orgId/repos/:repoId/settings": ( // optional: you can explicitly pass a type to BunRequest: req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">, ) => { const { orgId, repoId } = req.params; return Response.json({ orgId, repoId }); }, }, }); ``` Percent-encoded route parameter values are automatically decoded. Unicode characters are supported. Invalid unicode is replaced with the unicode replacement character `&0xFFFD;`. ### Static responses Routes can also be `Response` objects (without the handler function). Bun.serve() optimizes it for zero-allocation dispatch - perfect for health checks, redirects, and fixed content: ```ts Bun.serve({ routes: { // Health checks "/health": new Response("OK"), "/ready": new Response("Ready", { headers: { // Pass custom headers "X-Ready": "1", }, }), // Redirects "/blog": Response.redirect("https://bun.sh/blog"), // API responses "/api/config": Response.json({ version: "1.0.0", env: "production", }), }, }); ``` Static responses do not allocate additional memory after initialization. You can generally expect at least a 15% performance improvement over manually returning a `Response` object. Static route responses are cached for the lifetime of the server object. To reload static routes, call `server.reload(options)`. ```ts const server = Bun.serve({ static: { "/api/time": new Response(new Date().toISOString()), }, fetch(req) { return new Response("404!"); }, }); // Update the time every second. setInterval(() => { server.reload({ static: { "/api/time": new Response(new Date().toISOString()), }, fetch(req) { return new Response("404!"); }, }); }, 1000); ``` Reloading routes only impact the next request. In-flight requests continue to use the old routes. After in-flight requests to old routes are finished, the old routes are freed from memory. To simplify error handling, static routes do not support streaming response bodies from `ReadableStream` or an `AsyncIterator`. Fortunately, you can still buffer the response in memory first: ```ts const time = await fetch("https://api.example.com/v1/data"); // Buffer the response in memory first. const blob = await time.blob(); const server = Bun.serve({ static: { "/api/data": new Response(blob), }, fetch(req) { return new Response("404!"); }, }); ``` ### Route precedence Routes are matched in order of specificity: 1. Exact routes (`/users/all`) 2. Parameter routes (`/users/:id`) 3. Wildcard routes (`/users/*`) 4. Global catch-all (`/*`) ```ts Bun.serve({ routes: { // Most specific first "/api/users/me": () => new Response("Current user"), "/api/users/:id": req => new Response(`User ${req.params.id}`), "/api/*": () => new Response("API catch-all"), "/*": () => new Response("Global catch-all"), }, }); ``` ### Per-HTTP Method Routes Route handlers can be specialized by HTTP method: ```ts Bun.serve({ routes: { "/api/posts": { // Different handlers per method GET: () => new Response("List posts"), POST: async req => { const post = await req.json(); return Response.json({ id: crypto.randomUUID(), ...post }); }, PUT: async req => { const updates = await req.json(); return Response.json({ updated: true, ...updates }); }, DELETE: () => new Response(null, { status: 204 }), }, }, }); ``` You can pass any of the following methods: | Method | Usecase example | | --------- | ------------------------------- | | `GET` | Fetch a resource | | `HEAD` | Check if a resource exists | | `OPTIONS` | Get allowed HTTP methods (CORS) | | `DELETE` | Delete a resource | | `PATCH` | Update a resource | | `POST` | Create a resource | | `PUT` | Update a resource | When passing a function instead of an object, all methods will be handled by that function: ```ts const server = Bun.serve({ routes: { "/api/version": () => Response.json({ version: "1.0.0" }), }, }); await fetch(new URL("/api/version", server.url)); await fetch(new URL("/api/version", server.url), { method: "PUT" }); // ... etc ``` ### Hot Route Reloading Update routes without server restarts using `server.reload()`: ```ts const server = Bun.serve({ routes: { "/api/version": () => Response.json({ version: "1.0.0" }), }, }); // Deploy new routes without downtime server.reload({ routes: { "/api/version": () => Response.json({ version: "2.0.0" }), }, }); ``` ### Error Handling Bun provides structured error handling for routes: ```ts Bun.serve({ routes: { // Errors are caught automatically "/api/risky": () => { throw new Error("Something went wrong"); }, }, // Global error handler error(error) { console.error(error); return new Response(`Internal Error: ${error.message}`, { status: 500, headers: { "Content-Type": "text/plain", }, }); }, }); ``` ### HTML imports Bun supports importing HTML files directly into your server code, enabling full-stack applications with both server-side and client-side code. HTML imports work in two modes: **Development (`bun --hot`):** Assets are bundled on-demand at runtime, enabling hot module replacement (HMR) for a fast, iterative development experience. When you change your frontend code, the browser automatically updates without a full page reload. **Production (`bun build`):** When building with `bun build --target=bun`, the `import index from "./index.html"` statement resolves to a pre-built manifest object containing all bundled client assets. `Bun.serve` consumes this manifest to serve optimized assets with zero runtime bundling overhead. This is ideal for deploying to production. ```ts import myReactSinglePageApp from "./index.html"; Bun.serve({ routes: { "/": myReactSinglePageApp, }, }); ``` HTML imports don't just serve HTML — it's a full-featured frontend bundler, transpiler, and toolkit built using Bun's [bundler](https://bun.sh/docs/bundler), JavaScript transpiler and CSS parser. You can use this to build full-featured frontends with React, TypeScript, Tailwind CSS, and more. For a complete guide on building full-stack applications with HTML imports, including detailed examples and best practices, see [/docs/bundler/fullstack](https://bun.sh/docs/bundler/fullstack). ### Practical example: REST API Here's a basic database-backed REST API using Bun's router with zero dependencies: {% codetabs %} ```ts#server.ts import type { Post } from "./types.ts"; import { Database } from "bun:sqlite"; const db = new Database("posts.db"); db.exec(` CREATE TABLE IF NOT EXISTS posts ( id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL, created_at TEXT NOT NULL ) `); Bun.serve({ routes: { // List posts "/api/posts": { GET: () => { const posts = db.query("SELECT * FROM posts").all(); return Response.json(posts); }, // Create post POST: async req => { const post: Omit<Post, "id" | "created_at"> = await req.json(); const id = crypto.randomUUID(); db.query( `INSERT INTO posts (id, title, content, created_at) VALUES (?, ?, ?, ?)`, ).run(id, post.title, post.content, new Date().toISOString()); return Response.json({ id, ...post }, { status: 201 }); }, }, // Get post by ID "/api/posts/:id": req => { const post = db .query("SELECT * FROM posts WHERE id = ?") .get(req.params.id); if (!post) { return new Response("Not Found", { status: 404 }); } return Response.json(post); }, }, error(error) { console.error(error); return new Response("Internal Server Error", { status: 500 }); }, }); ``` ```ts#types.ts export interface Post { id: string; title: string; content: string; created_at: string; } ``` {% /codetabs %} ### Routing performance `Bun.serve()`'s router builds on top uWebSocket's [tree-based approach](https://github.com/oven-sh/bun/blob/0d1a00fa0f7830f8ecd99c027fce8096c9d459b6/packages/bun-uws/src/HttpRouter.h#L57-L64) to add [SIMD-accelerated route parameter decoding](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/decodeURIComponentSIMD.cpp#L21-L271) and [JavaScriptCore structure caching](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/ServerRouteList.cpp#L100-L101) to push the performance limits of what modern hardware allows. ### `fetch` request handler The `fetch` handler handles incoming requests that weren't matched by any route. It receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or [`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). ```ts 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!"); }, }); ``` The `fetch` handler supports async/await: ```ts import { sleep, serve } from "bun"; serve({ async fetch(req) { const start = performance.now(); await sleep(10); const end = performance.now(); return new Response(`Slept for ${end - start}ms`); }, }); ``` Promise-based responses are also supported: ```ts Bun.serve({ fetch(req) { // Forward the request to another server. return fetch("https://example.com"); }, }); ``` You can also access the `Server` object from the `fetch` handler. It's the second argument passed to the `fetch` function. ```ts // `server` is passed in as the second argument to `fetch`. const server = Bun.serve({ fetch(req, server) { const ip = server.requestIP(req); return new Response(`Your IP is ${ip}`); }, }); ``` ### Changing the `port` and `hostname` To configure which port and hostname the server will listen on, set `port` and `hostname` in the options object. ```ts Bun.serve({ port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000 hostname: "mydomain.com", // defaults to "0.0.0.0" fetch(req) { return new Response("404!"); }, }); ``` To randomly select an available port, set `port` to `0`. ```ts const server = Bun.serve({ port: 0, // random port fetch(req) { return new Response("404!"); }, }); // server.port is the randomly selected port console.log(server.port); ``` You can view the chosen port by accessing the `port` property on the server object, or by accessing the `url` property. ```ts console.log(server.port); // 3000 console.log(server.url); // http://localhost:3000 ``` #### Configuring a default port Bun supports several options and environment variables to configure the default port. The default port is used when the `port` option is not set. - `--port` CLI flag ```sh $ bun --port=4002 server.ts ``` - `BUN_PORT` environment variable ```sh $ BUN_PORT=4002 bun server.ts ``` - `PORT` environment variable ```sh $ PORT=4002 bun server.ts ``` - `NODE_PORT` environment variable ```sh $ NODE_PORT=4002 bun server.ts ``` ### Unix domain sockets To listen on a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket), pass the `unix` option with the path to the socket. ```ts Bun.serve({ unix: "/tmp/my-socket.sock", // path to socket fetch(req) { return new Response(`404!`); }, }); ``` ### Abstract namespace sockets Bun supports Linux abstract namespace sockets. To use an abstract namespace socket, prefix the `unix` path with a null byte. ```ts Bun.serve({ unix: "\0my-abstract-socket", // abstract namespace socket fetch(req) { return new Response(`404!`); }, }); ``` Unlike unix domain sockets, abstract namespace sockets are not bound to the filesystem and are automatically removed when the last reference to the socket is closed. ## Error handling To activate development mode, set `development: true`. ```ts 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. {% image src="/images/exception_page.png" caption="Bun's built-in 500 page" /%} ### `error` callback To handle server-side errors, implement an `error` handler. This function should return a `Response` to serve to the client when an error occurs. This response will supersede Bun's default error page in `development` mode. ```ts Bun.serve({ fetch(req) { throw new Error("woops!"); }, error(error) { return new Response(`<pre>${error}\n${error.stack}</pre>`, { headers: { "Content-Type": "text/html", }, }); }, }); ``` {% callout %} [Learn more about debugging in Bun](https://bun.sh/docs/runtime/debugger) {% /callout %} The call to `Bun.serve` returns a `Server` object. To stop the server, call the `.stop()` method. ```ts const server = Bun.serve({ fetch() { return new Response("Bun!"); }, }); server.stop(); ``` ## TLS Bun supports TLS out of the box, powered by [BoringSSL](https://boringssl.googlesource.com/boringssl). Enable TLS by passing in a value for `key` and `cert`; both are required to enable TLS. ```ts-diff Bun.serve({ fetch(req) { return new Response("Hello!!!"); }, + tls: { + key: Bun.file("./key.pem"), + cert: Bun.file("./cert.pem"), + } }); ``` The `key` and `cert` fields expect the _contents_ of your TLS key and certificate, _not a path to it_. This can be a string, `BunFile`, `TypedArray`, or `Buffer`. ```ts Bun.serve({ fetch() {}, tls: { // BunFile key: Bun.file("./key.pem"), // Buffer key: fs.readFileSync("./key.pem"), // string key: fs.readFileSync("./key.pem", "utf8"), // array of above key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], }, }); ``` If your private key is encrypted with a passphrase, provide a value for `passphrase` to decrypt it. ```ts-diff Bun.serve({ fetch(req) { return new Response("Hello!!!"); }, tls: { key: Bun.file("./key.pem"), cert: Bun.file("./cert.pem"), + passphrase: "my-secret-passphrase", } }); ``` Optionally, you can override the trusted CA certificates by passing a value for `ca`. By default, the server will trust the list of well-known CAs curated by Mozilla. When `ca` is specified, the Mozilla list is overwritten. ```ts-diff Bun.serve({ fetch(req) { return new Response("Hello!!!"); }, tls: { key: Bun.file("./key.pem"), // path to TLS key cert: Bun.file("./cert.pem"), // path to TLS cert + ca: Bun.file("./ca.pem"), // path to root CA certificate } }); ``` To override Diffie-Hellman parameters: ```ts Bun.serve({ // ... tls: { // other config dhParamsFile: "/path/to/dhparams.pem", // path to Diffie Hellman parameters }, }); ``` ### Server name indication (SNI) To configure the server name indication (SNI) for the server, set the `serverName` field in the `tls` object. ```ts Bun.serve({ // ... tls: { // ... other config serverName: "my-server.com", // SNI }, }); ``` To allow multiple server names, pass an array of objects to `tls`, each with a `serverName` field. ```ts Bun.serve({ // ... tls: [ { key: Bun.file("./key1.pem"), cert: Bun.file("./cert1.pem"), serverName: "my-server1.com", }, { key: Bun.file("./key2.pem"), cert: Bun.file("./cert2.pem"), serverName: "my-server2.com", }, ], }); ``` ## idleTimeout To configure the idle timeout, set the `idleTimeout` field in Bun.serve. ```ts Bun.serve({ // 10 seconds: idleTimeout: 10, fetch(req) { return new Response("Bun!"); }, }); ``` This is the maximum amount of time a connection is allowed to be idle before the server closes it. A connection is idling if there is no data sent or received. ## export default syntax Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax. ```ts#server.ts 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 default` it. This file can be executed as-is; when Bun sees 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. --> <!-- ```bash $ 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](https://bun.sh/docs/runtime/hot). --> ## Streaming files To stream a file, return a `Response` object with a `BunFile` object as the body. ```ts Bun.serve({ fetch(req) { return new Response(Bun.file("./hello.txt")); }, }); ``` {% callout %} ⚡️ **Speed** — Bun automatically uses the [`sendfile(2)`](https://man7.org/linux/man-pages/man2/sendfile.2.html) system call when possible, enabling zero-copy file transfers in the kernel—the fastest way to send files. {% /callout %} You can send part of a file using the [`slice(start, end)`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice) method on the `Bun.file` object. This automatically sets the `Content-Range` and `Content-Length` headers on the `Response` object. ```ts 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)); }, }); ``` ## Server Lifecycle Methods ### server.stop() - Stop the server To stop the server from accepting new connections: ```ts const server = Bun.serve({ fetch(req) { return new Response("Hello!"); }, }); // Gracefully stop the server (waits for in-flight requests) await server.stop(); // Force stop and close all active connections await server.stop(true); ``` By default, `stop()` allows in-flight requests and WebSocket connections to complete. Pass `true` to immediately terminate all connections. ### server.ref() and server.unref() - Process lifecycle control Control whether the server keeps the Bun process alive: ```ts // Don't keep process alive if server is the only thing running server.unref(); // Restore default behavior - keep process alive server.ref(); ``` ### server.reload() - Hot reload handlers Update the server's handlers without restarting: ```ts const server = Bun.serve({ routes: { "/api/version": Response.json({ version: "v1" }), }, fetch(req) { return new Response("v1"); }, }); // Update to new handler server.reload({ routes: { "/api/version": Response.json({ version: "v2" }), }, fetch(req) { return new Response("v2"); }, }); ``` This is useful for development and hot reloading. Only `fetch`, `error`, and `routes` can be updated. ## Per-Request Controls <!-- ### server.abort(Request) - Abort requests The `server.abort(request: Request)` method: - Returns `true` if request was aborted, `false` if already aborted/completed - Triggers the request's `AbortSignal` - Cancels any attached `ReadableStream` - Rejects any pending body promises (like `.text()`) ```ts const server = Bun.serve({ fetch(req, server) { // abort if the url contains "slow" if (req.url.includes("slow")) { server.abort(req); // When aborted, the server will not error due to the lack of a `Response` object // If you return a `Response` anyway, it will be ignored. return; } return new Response("Processing..."); }, }); ``` --> ### server.timeout(Request, seconds) - Custom request timeouts Set a custom idle timeout for individual requests: ```ts const server = Bun.serve({ fetch(req, server) { // Set 60 second timeout for this request server.timeout(req, 60); // If they take longer than 60 seconds to send the body, the request will be aborted await req.text(); return new Response("Done!"); }, }); ``` Pass `0` to disable the timeout for a request. ### server.requestIP(Request) - Get client information Get client IP and port information: ```ts const server = Bun.serve({ fetch(req, server) { const address = server.requestIP(req); if (address) { return new Response( `Client IP: ${address.address}, Port: ${address.port}`, ); } return new Response("Unknown client"); }, }); ``` Returns `null` for closed requests or Unix domain sockets. ## Working with Cookies Bun provides a built-in API for working with cookies in HTTP requests and responses. The `BunRequest` object includes a `cookies` property that provides a `CookieMap` for easily accessing and manipulating cookies. When using `routes`, `Bun.serve()` automatically tracks `request.cookies.set` and applies them to the response. ### Reading cookies Read cookies from incoming requests using the `cookies` property on the `BunRequest` object: ```ts Bun.serve({ routes: { "/profile": req => { // Access cookies from the request const userId = req.cookies.get("user_id"); const theme = req.cookies.get("theme") || "light"; return Response.json({ userId, theme, message: "Profile page", }); }, }, }); ``` ### Setting cookies To set cookies, use the `set` method on the `CookieMap` from the `BunRequest` object. ```ts Bun.serve({ routes: { "/login": req => { const cookies = req.cookies; // Set a cookie with various options cookies.set("user_id", "12345", { maxAge: 60 * 60 * 24 * 7, // 1 week httpOnly: true, secure: true, path: "/", }); // Add a theme preference cookie cookies.set("theme", "dark"); // Modified cookies from the request are automatically applied to the response return new Response("Login successful"); }, }, }); ``` `Bun.serve()` automatically tracks modified cookies from the request and applies them to the response. ### Deleting cookies To delete a cookie, use the `delete` method on the `request.cookies` (`CookieMap`) object: ```ts Bun.serve({ routes: { "/logout": req => { // Delete the user_id cookie req.cookies.delete("user_id", { path: "/", }); return new Response("Logged out successfully"); }, }, }); ``` Deleted cookies become a `Set-Cookie` header on the response with the `maxAge` set to `0` and an empty `value`. ## Server Metrics ### server.pendingRequests and server.pendingWebSockets Monitor server activity with built-in counters: ```ts const server = Bun.serve({ fetch(req, server) { return new Response( `Active requests: ${server.pendingRequests}\n` + `Active WebSockets: ${server.pendingWebSockets}`, ); }, }); ``` ### server.subscriberCount(topic) - WebSocket subscribers Get count of subscribers for a WebSocket topic: ```ts const server = Bun.serve({ fetch(req, server) { const chatUsers = server.subscriberCount("chat"); return new Response(`${chatUsers} users in chat`); }, websocket: { message(ws) { ws.subscribe("chat"); }, }, }); ``` ## WebSocket Configuration ### server.publish(topic, data, compress) - WebSocket Message Publishing The server can publish messages to all WebSocket clients subscribed to a topic: ```ts const server = Bun.serve({ websocket: { message(ws) { // Publish to all "chat" subscribers server.publish("chat", "Hello everyone!"); }, }, fetch(req) { // ... }, }); ``` The `publish()` method returns: - Number of bytes sent if successful - `0` if the message was dropped - `-1` if backpressure was applied ### WebSocket Handler Options When configuring WebSockets, several advanced options are available through the `websocket` handler: ```ts Bun.serve({ websocket: { // Maximum message size (in bytes) maxPayloadLength: 64 * 1024, // Backpressure limit before messages are dropped backpressureLimit: 1024 * 1024, // Close connection if backpressure limit is hit closeOnBackpressureLimit: true, // Handler called when backpressure is relieved drain(ws) { console.log("Backpressure relieved"); }, // Enable per-message deflate compression perMessageDeflate: { compress: true, decompress: true, }, // Send ping frames to keep connection alive sendPings: true, // Handlers for ping/pong frames ping(ws, data) { console.log("Received ping"); }, pong(ws, data) { console.log("Received pong"); }, // Whether server receives its own published messages publishToSelf: false, }, }); ``` ## Benchmarks Below are Bun and Node.js implementations of a simple HTTP server that responds `Bun!` to each incoming `Request`. {% codetabs %} ```ts#Bun Bun.serve({ fetch(req: Request) { return new Response("Bun!"); }, port: 3000, }); ``` ```ts#Node require("http") .createServer((req, res) => res.end("Bun!")) .listen(8080); ``` {% /codetabs %} The `Bun.serve` server can handle roughly 2.5x more requests per second than Node.js on Linux. {% table %} - Runtime - Requests per second --- - Node 16 - ~64,000 --- - Bun - ~160,000 {% /table %} {% image width="499" alt="image" src="https://user-images.githubusercontent.com/709451/162389032-fc302444-9d03-46be-ba87-c12bd8ce89a0.png" /%} ## Reference {% details summary="See TypeScript definitions" %} ```ts interface Server extends Disposable { /** * Stop the server from accepting new connections. * @param closeActiveConnections If true, immediately terminates all connections * @returns Promise that resolves when the server has stopped */ stop(closeActiveConnections?: boolean): Promise<void>; /** * Update handlers without restarting the server. * Only fetch and error handlers can be updated. */ reload(options: Serve): void; /** * Make a request to the running server. * Useful for testing or internal routing. */ fetch(request: Request | string): Response | Promise<Response>; /** * Upgrade an HTTP request to a WebSocket connection. * @returns true if upgrade successful, false if failed */ upgrade<T = undefined>( request: Request, options?: { headers?: Bun.HeadersInit; data?: T; }, ): boolean; /** * Publish a message to all WebSocket clients subscribed to a topic. * @returns Bytes sent, 0 if dropped, -1 if backpressure applied */ publish( topic: string, data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, compress?: boolean, ): ServerWebSocketSendStatus; /** * Get count of WebSocket clients subscribed to a topic. */ subscriberCount(topic: string): number; /** * Get client IP address and port. * @returns null for closed requests or Unix sockets */ requestIP(request: Request): SocketAddress | null; /** * Set custom idle timeout for a request. * @param seconds Timeout in seconds, 0 to disable */ timeout(request: Request, seconds: number): void; /** * Keep process alive while server is running. */ ref(): void; /** * Allow process to exit if server is only thing running. */ unref(): void; /** Number of in-flight HTTP requests */ readonly pendingRequests: number; /** Number of active WebSocket connections */ readonly pendingWebSockets: number; /** Server URL including protocol, hostname and port */ readonly url: URL; /** Port server is listening on */ readonly port: number; /** Hostname server is bound to */ readonly hostname: string; /** Whether server is in development mode */ readonly development: boolean; /** Server instance identifier */ readonly id: string; } interface WebSocketHandler<T = undefined> { /** Maximum WebSocket message size in bytes */ maxPayloadLength?: number; /** Bytes of queued messages before applying backpressure */ backpressureLimit?: number; /** Whether to close connection when backpressure limit hit */ closeOnBackpressureLimit?: boolean; /** Called when backpressure is relieved */ drain?(ws: ServerWebSocket<T>): void | Promise<void>; /** Seconds before idle timeout */ idleTimeout?: number; /** Enable per-message deflate compression */ perMessageDeflate?: | boolean | { compress?: WebSocketCompressor | boolean; decompress?: WebSocketCompressor | boolean; }; /** Send ping frames to keep connection alive */ sendPings?: boolean; /** Whether server receives its own published messages */ publishToSelf?: boolean; /** Called when connection opened */ open?(ws: ServerWebSocket<T>): void | Promise<void>; /** Called when message received */ message( ws: ServerWebSocket<T>, message: string | Buffer, ): void | Promise<void>; /** Called when connection closed */ close?( ws: ServerWebSocket<T>, code: number, reason: string, ): void | Promise<void>; /** Called when ping frame received */ ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>; /** Called when pong frame received */ pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>; } interface TLSOptions { /** Certificate authority chain */ ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>; /** Server certificate */ cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>; /** Path to DH parameters file */ dhParamsFile?: string; /** Private key */ key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>; /** Reduce TLS memory usage */ lowMemoryMode?: boolean; /** Private key passphrase */ passphrase?: string; /** OpenSSL options flags */ secureOptions?: number; /** Server name for SNI */ serverName?: string; } ``` {% /details %}