Puoi aggiungere route a Bun.serve() usando la proprietà routes (per percorsi statici parametri e wildcard) o gestendo le richieste non corrispondenti con il metodo fetch.
Il router di Bun.serve() si basa sull'approccio tree-based di uWebSocket per aggiungere decodifica dei parametri di route accelerata via SIMD e caching della struttura JavaScriptCore per spingere i limiti delle prestazioni di ciò che l'hardware moderno consente.
Configurazione di base
Bun.serve({
routes: {
"/": () => new Response("Home"),
"/api": () => Response.json({ success: true }),
"/users": async () => Response.json({ users: [] }),
},
fetch() {
return new Response("Route non corrispondente");
},
});Le route in Bun.serve() ricevono un BunRequest (che estende Request) e restituiscono una Response o Promise<Response>. Questo rende più facile usare lo stesso codice sia per inviare che per ricevere richieste HTTP.
// Semplificato per brevità
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}Route asincrone
Async/await
Puoi usare async/await negli handler di route per restituire una Promise<Response>.
import { sql, serve } from "bun";
serve({
port: 3001,
routes: {
"/api/version": async () => {
const [version] = await sql`SELECT version()`;
return Response.json(version);
},
},
});Promise
Puoi anche restituire una Promise<Response> da un handler di route.
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);
});
},
},
});Precedenza delle route
Le route sono corrispondenti in ordine di specificità:
- Route esatte (
/users/all) - Route con parametri (
/users/:id) - Route wildcard (
/users/*) - Catch-all globale (
/*)
Bun.serve({
routes: {
// Più specifico per primo
"/api/users/me": () => new Response("Utente corrente"),
"/api/users/:id": req => new Response(`Utente ${req.params.id}`),
"/api/*": () => new Response("API catch-all"),
"/*": () => new Response("Catch-all globale"),
},
});Parametri di route type-safe
TypeScript analizza i parametri di route quando passati come letterali di stringa, così il tuo editor mostrerà l'autocompletamento quando accedi a request.params.
import type { BunRequest } from "bun";
Bun.serve({
routes: {
// TypeScript conosce la forma di params quando passato come letterale di stringa
"/orgs/:orgId/repos/:repoId": req => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
"/orgs/:orgId/repos/:repoId/settings": (
// opzionale: puoi passare esplicitamente un tipo a BunRequest:
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
) => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
},
});I valori dei parametri di route percent-encoded sono automaticamente decodificati. I caratteri Unicode sono supportati. L'unicode non valido è sostituito con il carattere di sostituzione unicode &0xFFFD;.
Risposte statiche
Le route possono anche essere oggetti Response (senza la funzione handler). Bun.serve() le ottimizza per dispatch a allocazione zero - perfetto per health check redirect e contenuto fisso:
Bun.serve({
routes: {
// Health checks
"/health": new Response("OK"),
"/ready": new Response("Ready", {
headers: {
// Passa header personalizzati
"X-Ready": "1",
},
}),
// Redirect
"/blog": Response.redirect("https://bun.com/blog"),
// Risposte API
"/api/config": Response.json({
version: "1.0.0",
env: "production",
}),
},
});Le risposte di route statiche non allocano memoria aggiuntiva dopo l'inizializzazione. Puoi generalmente aspettarti almeno un miglioramento delle prestazioni del 15% rispetto al restituire manualmente un oggetto Response.
Le risposte di route statiche sono memorizzate nella cache per la durata dell'oggetto server. Per ricaricare le route statiche, chiama server.reload(options).
Risposte di file vs Risposte statiche
Quando servi file nelle route, ci sono due comportamenti distinti a seconda se bufferizzi il contenuto del file o lo servi direttamente:
Bun.serve({
routes: {
// Route statica - il contenuto è bufferizzato in memoria all'avvio
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
// Route di file - il contenuto è letto dal filesystem su ogni richiesta
"/download.zip": new Response(Bun.file("./download.zip")),
},
});Route statiche (new Response(await file.bytes())) bufferizzano il contenuto in memoria all'avvio:
- Zero I/O del filesystem durante le richieste - il contenuto è servito interamente dalla memoria
- Supporto ETag - Genera e valida automaticamente gli ETag per il caching
- If-None-Match - Restituisce
304 Not Modifiedquando l'ETag del client corrisponde - Nessuna gestione 404 - I file mancanti causano errori all'avvio, non 404 a runtime
- Uso della memoria - Contenuto completo del file archiviato in RAM
- Ideale per: Piccoli asset statici risposte API file accessed frequentemente
Route di file (new Response(Bun.file(path))) leggono dal filesystem per ogni richiesta:
- Letture del filesystem su ogni richiesta - controlla l'esistenza del file e legge il contenuto
- Gestione 404 built-in - Restituisce
404 Not Foundse il file non esiste o diventa inaccessibile - Supporto Last-Modified - Usa il tempo di modifica del file per gli header
If-Modified-Since - If-Modified-Since - Restituisce
304 Not Modifiedquando il file non è cambiato dalla versione cached del client - Supporto range request - Gestisce automaticamente le richieste di contenuto parziale con gli header
Content-Range - Trasferimenti streaming - Usa un lettore bufferizzato con gestione backpressure per un uso efficiente della memoria
- Efficiente in memoria - Bufferizza solo piccoli chunk durante il trasferimento, non l'intero file
- Ideale per: File di grandi dimensioni contenuti dinamici upload utente file che cambiano frequentemente
Streaming di file
Per streammare un file, restituisci un oggetto Response con un oggetto BunFile come body.
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});Puoi inviare parte di un file usando il metodo slice(start, end) sull'oggetto Bun.file. Questo imposta automaticamente gli header Content-Range e Content-Length sull'oggetto Response.
Bun.serve({
fetch(req) {
// analizza l'header `Range`
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]
// restituisci una slice del file
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});Handler di richiesta fetch
L'handler fetch gestisce le richieste in arrivo che non sono state corrispondenti da alcuna route. Riceve un oggetto Request e restituisce una Response o Promise<Response>.
Bun.serve({
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response("Pagina home!");
if (url.pathname === "/blog") return new Response("Blog!");
return new Response("404!");
},
});L'handler fetch supporta 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(`In sleep per ${end - start}ms`);
},
});Anche le risposte basate su Promise sono supportate:
Bun.serve({
fetch(req) {
// Inoltra la richiesta a un altro server.
return fetch("https://example.com");
},
});Puoi anche accedere all'oggetto Server dall'handler fetch. È il secondo argomento passato alla funzione fetch.
// `server` è passato come secondo argomento a `fetch`.
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`Il tuo IP è ${ip.address}`);
},
});