Sie können Routen zu Bun.serve() hinzufügen, indem Sie die routes-Eigenschaft verwenden (für statische Pfade, Parameter und Wildcards) oder nicht übereinstimmende Anfragen mit der fetch-Methode behandeln.
Der Router von Bun.serve() baut auf dem baumbasierten Ansatz von uWebSocket auf, um SIMD-beschleungte Routenparameter-Dekodierung und JavaScriptCore-Struktur-Caching hinzuzufügen, um die Leistungsgrenzen dessen zu erweitern, was moderne Hardware ermöglicht.
Grundlegende Einrichtung
Bun.serve({
routes: {
"/": () => new Response("Startseite"),
"/api": () => Response.json({ success: true }),
"/users": async () => Response.json({ users: [] }),
},
fetch() {
return new Response("Nicht übereinstimmende Route");
},
});Routen in Bun.serve() erhalten eine BunRequest (die Request erweitert) und geben eine Response oder Promise<Response> zurück. Dies erleichtert die Verwendung desselben Codes zum Senden und Empfangen von HTTP-Anfragen.
// Zur Kürze vereinfacht
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}Asynchrone Routen
Async/await
Sie können async/await in Routenhandlern verwenden, um eine Promise<Response> zurückzugeben.
import { sql, serve } from "bun";
serve({
port: 3001,
routes: {
"/api/version": async () => {
const [version] = await sql`SELECT version()`;
return Response.json(version);
},
},
});Promise
Sie können auch eine Promise<Response> von einem Routenhandler zurückgeben.
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);
});
},
},
});Routenpriorität
Routen werden in der Reihenfolge der Spezifität abgeglichen:
- Exakte Routen (
/users/all) - Parameterrouten (
/users/:id) - Wildcard-Routen (
/users/*) - Globale Catch-All-Route (
/*)
Bun.serve({
routes: {
// Am spezifischsten zuerst
"/api/users/me": () => new Response("Aktueller Benutzer"),
"/api/users/:id": req => new Response(`Benutzer ${req.params.id}`),
"/api/*": () => new Response("API-Catch-All"),
"/*": () => new Response("Globaler Catch-All"),
},
});Typsichere Routenparameter
TypeScript analysiert Routenparameter, wenn sie als String-Literal übergeben werden, sodass Ihr Editor Autovervollständigung beim Zugriff auf request.params anzeigt.
import type { BunRequest } from "bun";
Bun.serve({
routes: {
// TypeScript kennt die Form von params, wenn als String-Literal übergeben
"/orgs/:orgId/repos/:repoId": req => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
"/orgs/:orgId/repos/:repoId/settings": (
// optional: Sie können explizit einen Typ an BunRequest übergeben:
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
) => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
},
});Prozent-kodierte Routenparameterwerte werden automatisch dekodiert. Unicode-Zeichen werden unterstützt. Ungültiges Unicode wird durch das Unicode-Ersatzzeichen &0xFFFD; ersetzt.
Statische Antworten
Routen können auch Response-Objekte sein (ohne die Handler-Funktion). Bun.serve() optimiert dies für die Null-Allokation-Dispatch – perfekt für Health-Checks, Weiterleitungen und festen Inhalt:
Bun.serve({
routes: {
// Health-Checks
"/health": new Response("OK"),
"/ready": new Response("Bereit", {
headers: {
// Benutzerdefinierte Header übergeben
"X-Ready": "1",
},
}),
// Weiterleitungen
"/blog": Response.redirect("https://bun.com/blog"),
// API-Antworten
"/api/config": Response.json({
version: "1.0.0",
env: "production",
}),
},
});Statische Antworten allozieren nach der Initialisierung keinen zusätzlichen Speicher. Sie können im Allgemeinen mindestens eine 15%ige Leistungsverbesserung gegenüber dem manuellen Zurückgeben eines Response-Objekts erwarten.
Statische Routenantworten werden für die Lebensdauer des Server-Objekts zwischengespeichert. Um statische Routen neu zu laden, rufen Sie server.reload(options) auf.
Datei-Antworten vs. statische Antworten
Beim Bereitstellen von Dateien in Routen gibt es zwei unterschiedliche Verhaltensweisen, je nachdem, ob Sie den Dateiinhalt puffern oder direkt bereitstellen:
Bun.serve({
routes: {
// Statische Route - Inhalt wird beim Start im Speicher gepuffert
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
// Datei-Route - Inhalt wird bei jeder Anfrage vom Dateisystem gelesen
"/download.zip": new Response(Bun.file("./download.zip")),
},
});Statische Routen (new Response(await file.bytes())) puffern Inhalt beim Start im Speicher:
- Kein Dateisystem-I/O während Anfragen – Inhalt wird vollständig aus dem Speicher bereitgestellt
- ETag-Unterstützung – Generiert und validiert automatisch ETags für Caching
- If-None-Match – Gibt
304 Not Modifiedzurück, wenn Client-ETag übereinstimmt - Keine 404-Behandlung – Fehlende Dateien verursachen Startfehler, nicht Laufzeit-404s
- Speichernutzung – Vollständiger Dateiinhalt im RAM gespeichert
- Am besten für: Kleine statische Assets, API-Antworten, häufig abgerufene Dateien
Datei-Routen (new Response(Bun.file(path))) lesen bei jeder Anfrage vom Dateisystem:
- Dateisystem-Lesevorgänge bei jeder Anfrage – prüft Dateiexistenz und liest Inhalt
- Integrierte 404-Behandlung – Gibt
404 Not Foundzurück, wenn Datei nicht existiert oder nicht zugänglich ist - Last-Modified-Unterstützung – Verwendet Datei-Änderungszeit für
If-Modified-Since-Header - If-Modified-Since – Gibt
304 Not Modifiedzurück, wenn Datei sich seit der im Client-Cache gespeicherten Version nicht geändert hat - Range-Request-Unterstützung – Verarbeitet automatisch Teilinhalt-Anfragen mit
Content-Range-Headern - Streaming-Übertragungen – Verwendet gepufferten Leser mit Backpressure-Handling für effiziente Speichernutzung
- Speichereffizient – Puffert nur kleineChunks während der Übertragung, nicht die gesamte Datei
- Am besten für: Große Dateien, dynamische Inhalte, Benutzer-Uploads, Dateien, die sich häufig ändern
Streaming von Dateien
Um eine Datei zu streamen, geben Sie ein Response-Objekt mit einem BunFile-Objekt als Body zurück.
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});Sie können einen Teil einer Datei mit der slice(start, end)-Methode auf dem Bun.file-Objekt senden. Dies setzt automatisch die Content-Range- und Content-Length-Header auf dem Response-Objekt.
Bun.serve({
fetch(req) {
// `Range`-Header parsen
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]
// einen Ausschnitt der Datei zurückgeben
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});fetch Anfrage-Handler
Der fetch-Handler behandelt eingehende Anfragen, die von keiner Route übereinstimmt wurden. Er erhält ein Request-Objekt und gibt eine Response oder Promise<Response> zurück.
Bun.serve({
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response("Startseite!");
if (url.pathname === "/blog") return new Response("Blog!");
return new Response("404!");
},
});Der fetch-Handler unterstützt async/await:
import { sleep, serve } from "bun";
serve({
async fetch(req) {
const start = performance.now();
await sleep(10);
const end = performance.now();
return new Response(`Für ${end - start}ms geschlafen`);
},
});Promise-basierte Antworten werden ebenfalls unterstützt:
Bun.serve({
fetch(req) {
// Die Anfrage an einen anderen Server weiterleiten.
return fetch("https://example.com");
},
});Sie können auch auf das Server-Objekt vom fetch-Handler zugreifen. Es ist das zweite Argument, das an die fetch-Funktion übergeben wird.
// `server` wird als zweites Argument an `fetch` übergeben.
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`Ihre IP ist ${ip.address}`);
},
});