Bun.serve() with the routes property (static paths, parameters, and wildcards), or handle unmatched requests with the fetch method.
Bun.serve()’s router builds on top of uWebSocket’s tree-based approach. The router adds SIMD-accelerated route parameter decoding and JavaScriptCore structure caching to push the performance limits of what modern hardware allows.
Basic Setup
server.ts
Bun.serve() receive a BunRequest (which extends Request) and return a Response or Promise<Response>. Because routes use these Request and Response types, it is easier to use the same code for both sending and receiving HTTP requests.
Asynchronous Routes
Async/await
Use async/await in route handlers to return aPromise<Response>.
Promise
You can also return aPromise<Response> from a route handler.
Route precedence
Bun matches routes in order of specificity:- Exact routes (
/users/all) - Parameter routes (
/users/:id) - Wildcard routes (
/users/*) - Global catch-all (
/*)
Type-safe route parameters
TypeScript parses route parameters when passed as a string literal, so your editor shows autocomplete when accessingrequest.params.
index.ts
\uFFFD).
Static responses
Routes can also beResponse objects (without the handler function). Bun.serve() optimizes them for zero-allocation dispatch, which suits health checks, redirects, and fixed content:
Response object.
Bun caches static route responses for the lifetime of the server object. To reload static routes, call server.reload(options).
File Responses vs Static Responses
Serving a file from a route behaves differently depending on whether you buffer the file content or serve it directly:new Response(await file.bytes())) buffer content in memory at startup:
- Zero filesystem I/O during requests - content served entirely from memory
- ETag support - Automatically generates and validates ETags for caching
- If-None-Match - Returns
304 Not Modifiedwhen client ETag matches - No 404 handling - Missing files cause startup errors, not runtime 404s
- Memory usage - Full file content stored in RAM
- Best for: Small static assets, API responses, frequently accessed files
new Response(Bun.file(path))) read from filesystem per request:
- Filesystem reads on each request - checks file existence and reads content
- Built-in 404 handling - Returns
404 Not Foundif file doesn’t exist or becomes inaccessible - Last-Modified support - Uses file modification time for
If-Modified-Sinceheaders - If-Modified-Since - Returns
304 Not Modifiedwhen file hasn’t changed since client’s cached version - Range request support - Automatically handles partial content requests with
Content-Rangeheaders - Streaming transfers - Uses buffered reader with backpressure handling for efficient memory usage
- Memory efficient - Only buffers small chunks during transfer, not entire file
- Best for: Large files, dynamic content, user uploads, files that change frequently
Directory routes
To serve an entire directory tree at a URL prefix, pass{ dir } as the route value. The route path must end in /*.
dir. Bun rejects non-canonical paths with 404, so the served path is always the path the router matched. A path is non-canonical if it contains ., .., empty segments, %2F, or a %XX sequence encoding a character that may appear literally in a path segment. On Linux the open uses openat2(RESOLVE_IN_ROOT), so the kernel clamps symlinks that would escape dir.
Routing is case-sensitive, but filesystems on macOS and Windows are case-insensitive by default. As a result, a
case-varied URL (
/static/Admin/secret.txt) routes to the directory wildcard rather than a sibling /static/admin/*
handler and still opens admin/secret.txt. As with nginx, Caddy, and other static file servers, keep
access-controlled content outside dir rather than relying on an overlapping route to gate it.- Content-Type is set from the file extension.
- Last-Modified and a weak
ETag(W/"<size>-<mtime>") are sent on every response, andIf-Modified-Since/If-None-Matchare honored with304 Not Modified. - Range requests are supported with
Accept-Ranges: bytesandContent-Range. - A request that resolves to a directory without a trailing
/receives a301redirect to the trailing-slash URL. With the trailing slash, Bun servesindex.htmlfrom that directory. - Missing files return
404.
statCache: false to disable the per-path Last-Modified cache (saves roughly 20 KB per route).
Streaming files
To stream a file, return aResponse object with a BunFile object as the body.
⚡️ 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.slice(start, end) method on the Bun.file object. Bun sets the Content-Range and Content-Length headers on the Response object automatically.
fetch request handler
The fetch handler runs for incoming requests that no route matched. It receives a Request object and returns a Response or Promise<Response>.
fetch handler supports async/await:
fetch handler also receives the Server object as its second argument.