Skip to content

Você pode adicionar rotas ao Bun.serve() usando a propriedade routes (para caminhos estáticos, parâmetros e wildcards) ou lidando com requisições não correspondidas com o método fetch.

O roteador do Bun.serve() constrói sobre a abordagem baseada em árvore do uWebSocket para adicionar decodificação de parâmetros de rota acelerada por SIMD e cache de estrutura do JavaScriptCore para levar a performance aos limites do que o hardware moderno permite.

Configuração Básica

server.ts
ts
Bun.serve({
  routes: {
    "/": () => new Response("Home"),
    "/api": () => Response.json({ success: true }),
    "/users": async () => Response.json({ users: [] }),
  },
  fetch() {
    return new Response("Rota não correspondida");
  },
});

Rotas em Bun.serve() recebem um BunRequest (que estende Request) e retornam uma Response ou Promise<Response>. Isto facilita usar o mesmo código para enviar e receber requisições HTTP.

ts
// Simplificado para brevidade
interface BunRequest<T extends string> extends Request {
  params: Record<T, string>;
  readonly cookies: CookieMap;
}

Rotas Assíncronas

Async/await

Você pode usar async/await em handlers de rota para retornar uma 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

Você também pode retornar uma Promise<Response> de um handler de rota.

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);
      });
    },
  },
});

Precedência de Rotas

Rotas são correspondidas em ordem de especificidade:

  1. Rotas exatas (/users/all)
  2. Rotas com parâmetros (/users/:id)
  3. Rotas wildcard (/users/*)
  4. Global catch-all (/*)
ts
Bun.serve({
  routes: {
    // Mais específico primeiro
    "/api/users/me": () => new Response("Usuário atual"),
    "/api/users/:id": req => new Response(`Usuário ${req.params.id}`),
    "/api/*": () => new Response("API catch-all"),
    "/*": () => new Response("Global catch-all"),
  },
});

Parâmetros de Rota Type-safe

TypeScript analisa parâmetros de rota quando passados como um string literal, então seu editor mostrará autocomplete ao acessar request.params.

index.ts
ts
import type { BunRequest } from "bun";

Bun.serve({
  routes: {
    // TypeScript conhece a forma dos params quando passado como string literal
    "/orgs/:orgId/repos/:repoId": req => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },

    "/orgs/:orgId/repos/:repoId/settings": (
      // opcional: você pode explicitamente passar um tipo para BunRequest:
      req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
    ) => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },
  },
});

Valores de parâmetros de rota percent-encoded são automaticamente decodificados. Caracteres Unicode são suportados. Unicode inválido é substituído pelo caractere de substituição Unicode &0xFFFD;.

Respostas Estáticas

Rotas também podem ser objetos Response (sem a função handler). Bun.serve() otimiza para dispatch de zero alocação - perfeito para health checks, redirecionamentos e conteúdo fixo:

ts
Bun.serve({
  routes: {
    // Health checks
    "/health": new Response("OK"),
    "/ready": new Response("Ready", {
      headers: {
        // Passa headers customizados
        "X-Ready": "1",
      },
    }),

    // Redirecionamentos
    "/blog": Response.redirect("https://bun.com/blog"),

    // Respostas de API
    "/api/config": Response.json({
      version: "1.0.0",
      env: "production",
    }),
  },
});

Respostas de rotas estáticas não alocam memória adicional após inicialização. Você pode geralmente esperar pelo menos 15% de melhoria de performance sobre retornar manualmente um objeto Response.

Respostas de rotas estáticas são cacheadas durante o lifetime do objeto server. Para recarregar rotas estáticas, chame server.reload(options).

Respostas de Arquivo vs Respostas Estáticas

Ao servir arquivos em rotas, existem dois comportamentos distintos dependendo se você faz buffer do conteúdo do arquivo ou serve diretamente:

ts
Bun.serve({
  routes: {
    // Rota estática - conteúdo é bufferizado em memória na inicialização
    "/logo.png": new Response(await Bun.file("./logo.png").bytes()),

    // Rota de arquivo - conteúdo é lido do filesystem em cada requisição
    "/download.zip": new Response(Bun.file("./download.zip")),
  },
});

Rotas estáticas (new Response(await file.bytes())) bufferizam conteúdo em memória na inicialização:

  • Zero I/O de filesystem durante requisições - conteúdo servido inteiramente da memória
  • Suporte a ETag - Automaticamente gera e valida ETags para caching
  • If-None-Match - Retorna 304 Not Modified quando ETag do cliente corresponde
  • Sem tratamento de 404 - Arquivos ausentes causam erros na inicialização, não 404s em runtime
  • Uso de memória - Conteúdo completo do arquivo armazenado em RAM
  • Melhor para: Assets estáticos pequenos, respostas de API, arquivos frequentemente acessados

Rotas de arquivo (new Response(Bun.file(path))) leem do filesystem por requisição:

  • Leituras de filesystem em cada requisição - verifica existência do arquivo e lê conteúdo
  • Tratamento de 404 built-in - Retorna 404 Not Found se arquivo não existe ou se torna inacessível
  • Suporte a Last-Modified - Usa tempo de modificação do arquivo para headers If-Modified-Since
  • If-Modified-Since - Retorna 304 Not Modified quando arquivo não mudou desde versão cacheada do cliente
  • Suporte a range requests - Automaticamente lida com requisições de conteúdo parcial com headers Content-Range
  • Transferências streaming - Usa leitor bufferizado com tratamento de backpressure para uso eficiente de memória
  • Eficiente em memória - Apenas bufferiza pequenos chunks durante transferência, não arquivo inteiro
  • Melhor para: Arquivos grandes, conteúdo dinâmico, uploads de usuário, arquivos que mudam frequentemente

Streaming de Arquivos

Para fazer stream de um arquivo, retorne um objeto Response com um objeto BunFile como body.

ts
Bun.serve({
  fetch(req) {
    return new Response(Bun.file("./hello.txt"));
  },
});

Você pode enviar parte de um arquivo usando o método slice(start, end) no objeto Bun.file. Isto automaticamente define os headers Content-Range e Content-Length no objeto Response.

ts
Bun.serve({
  fetch(req) {
    // analisa 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]

    // retorna um slice do arquivo
    const bigFile = Bun.file("./big-video.mp4");
    return new Response(bigFile.slice(start, end));
  },
});

Handler de requisição fetch

O handler fetch lida com requisições recebidas que não foram correspondidas por nenhuma rota. Ele recebe um objeto Request e retorna uma Response ou Promise<Response>.

ts
Bun.serve({
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/") return new Response("Página inicial!");
    if (url.pathname === "/blog") return new Response("Blog!");
    return new Response("404!");
  },
});

O handler fetch suporta 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(`Dormiu por ${end - start}ms`);
  },
});

Respostas baseadas em Promise também são suportadas:

ts
Bun.serve({
  fetch(req) {
    // Encaminha a requisição para outro servidor.
    return fetch("https://example.com");
  },
});

Você também pode acessar o objeto Server do handler fetch. É o segundo argumento passado para a função fetch.

ts
// `server` é passado como segundo argumento para `fetch`.
const server = Bun.serve({
  fetch(req, server) {
    const ip = server.requestIP(req);
    return new Response(`Seu IP é ${ip.address}`);
  },
});

Bun by www.bunjs.com.cn edit