Puedes agregar rutas a Bun.serve() usando la propiedad routes (para rutas estáticas, parámetros y comodines) o manejando solicitudes no coincidentes con el método fetch.
El enrutador de Bun.serve() se construye sobre el enfoque basado en árbol de uWebSocket para agregar decodificación de parámetros de ruta acelerada por SIMD y caché de estructura de JavaScriptCore para llevar el rendimiento a los límites de lo que permite el hardware moderno.
Configuración Básica
Bun.serve({
routes: {
"/": () => new Response("Inicio"),
"/api": () => Response.json({ success: true }),
"/users": async () => Response.json({ users: [] }),
},
fetch() {
return new Response("Ruta no coincidente");
},
});Las rutas en Bun.serve() reciben un BunRequest (que extiende Request) y devuelven una Response o Promise<Response>. Esto facilita usar el mismo código tanto para enviar como recibir solicitudes HTTP.
// Simplificado para brevedad
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}Rutas Asíncronas
Async/await
Puedes usar async/await en manejadores de rutas para devolver 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
También puedes devolver una Promise<Response> desde un manejador de ruta.
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);
});
},
},
});Precedencia de Rutas
Las rutas se coinciden en orden de especificidad:
- Rutas exactas (
/users/all) - Rutas con parámetros (
/users/:id) - Rutas comodín (
/users/*) - Captura global (
/*)
Bun.serve({
routes: {
// Más específico primero
"/api/users/me": () => new Response("Usuario actual"),
"/api/users/:id": req => new Response(`Usuario ${req.params.id}`),
"/api/*": () => new Response("Captura de API"),
"/*": () => new Response("Captura global"),
},
});Parámetros de Ruta con Tipos
TypeScript analiza los parámetros de ruta cuando se pasan como un literal de cadena, así que tu editor mostrará autocompletado al acceder a request.params.
import type { BunRequest } from "bun";
Bun.serve({
routes: {
// TypeScript conoce la forma de params cuando se pasa como un literal de cadena
"/orgs/:orgId/repos/:repoId": req => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
"/orgs/:orgId/repos/:repoId/settings": (
// opcional: puedes pasar explícitamente un tipo a BunRequest:
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
) => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
},
});Los valores de parámetros de ruta codificados en porcentaje se decodifican automáticamente. Los caracteres Unicode son soportados. El unicode inválido se reemplaza con el carácter de reemplazo unicode &0xFFFD;.
Respuestas Estáticas
Las rutas también pueden ser objetos Response (sin la función manejadora). Bun.serve() lo optimiza para despacho de asignación cero, perfecto para verificaciones de estado, redirecciones y contenido fijo:
Bun.serve({
routes: {
// Verificaciones de estado
"/health": new Response("OK"),
"/ready": new Response("Listo", {
headers: {
// Pasar encabezados personalizados
"X-Ready": "1",
},
}),
// Redirecciones
"/blog": Response.redirect("https://bun.com/blog"),
// Respuestas de API
"/api/config": Response.json({
version: "1.0.0",
env: "production",
}),
},
});Las respuestas de rutas estáticas no asignan memoria adicional después de la inicialización. Generalmente puedes esperar al menos una mejora de rendimiento del 15% sobre devolver manualmente un objeto Response.
Las rutas estáticas se almacenan en caché durante la vida útil del objeto del servidor. Para recargar rutas estáticas, llama a server.reload(options).
Respuestas de Archivo vs Respuestas Estáticas
Al servir archivos en rutas, hay dos comportamientos distintos dependiendo de si almacenas en búfer el contenido del archivo o lo sirves directamente:
Bun.serve({
routes: {
// Ruta estática - el contenido se almacena en búfer en memoria al inicio
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
// Ruta de archivo - el contenido se lee del sistema de archivos en cada solicitud
"/download.zip": new Response(Bun.file("./download.zip")),
},
});Rutas estáticas (new Response(await file.bytes())) almacenan contenido en búfer en memoria al inicio:
- Cero E/S del sistema de archivos durante solicitudes - el contenido se sirve completamente desde memoria
- Soporte ETag - Genera y valida ETags automáticamente para caché
- If-None-Match - Devuelve
304 No Modificadocuando el ETag del cliente coincide - Sin manejo 404 - Los archivos faltantes causan errores de inicio, no 404 en tiempo de ejecución
- Uso de memoria - Contenido completo del archivo almacenado en RAM
- Mejor para: Activos estáticos pequeños, respuestas de API, archivos accedidos frecuentemente
Rutas de archivo (new Response(Bun.file(path))) leen del sistema de archivos por solicitud:
- Lecturas del sistema de archivos en cada solicitud - verifica existencia de archivo y lee contenido
- Manejo 404 integrado - Devuelve
404 No Encontradosi el archivo no existe o se vuelve inaccesible - Soporte Last-Modified - Usa tiempo de modificación del archivo para encabezados
If-Modified-Since - If-Modified-Since - Devuelve
304 No Modificadocuando el archivo no ha cambiado desde la versión en caché del cliente - Soporte de solicitudes de rango - Maneja automáticamente solicitudes de contenido parcial con encabezados
Content-Range - Transferencias de streaming - Usa lector con búfer con manejo de contrapresión para uso eficiente de memoria
- Eficiente en memoria - Solo almacena en búfer pequeños fragmentos durante la transferencia, no el archivo completo
- Mejor para: Archivos grandes, contenido dinámico, cargas de usuarios, archivos que cambian frecuentemente
Streaming de Archivos
Para transmitir un archivo, devuelve un objeto Response con un objeto BunFile como cuerpo.
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});Puedes enviar parte de un archivo usando el método slice(start, end) en el objeto Bun.file. Esto establece automáticamente los encabezados Content-Range y Content-Length en el objeto Response.
Bun.serve({
fetch(req) {
// analizar encabezado `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]
// devolver un slice del archivo
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});Manejador de Solicitudes fetch
El manejador fetch maneja solicitudes entrantes que no fueron coincidentes con ninguna ruta. Recibe un objeto Request y devuelve una Response o Promise<Response>.
Bun.serve({
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response("Página de inicio!");
if (url.pathname === "/blog") return new Response("Blog!");
return new Response("404!");
},
});El manejador fetch soporta 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(`Dormido por ${end - start}ms`);
},
});Las respuestas basadas en promesas también son soportadas:
Bun.serve({
fetch(req) {
// Reenviar la solicitud a otro servidor.
return fetch("https://example.com");
},
});También puedes acceder al objeto Server desde el manejador fetch. Es el segundo argumento pasado a la función fetch.
// `server` se pasa como segundo argumento a `fetch`.
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`Tu IP es ${ip.address}`);
},
});