Skip to content

Configuración Básica

index.ts
ts
const server = Bun.serve({
  // `routes` requiere Bun v1.2.3+
  routes: {
    // Rutas estáticas
    "/api/status": new Response("OK"),

    // Rutas dinámicas
    "/users/:id": req => {
      return new Response(`¡Hola Usuario ${req.params.id}!`);
    },

    // Manejadores por método HTTP
    "/api/posts": {
      GET: () => new Response("Listar posts"),
      POST: async req => {
        const body = await req.json();
        return Response.json({ created: true, ...body });
      },
    },

    // Ruta comodín para todas las rutas que comienzan con "/api/" y no coinciden de otra manera
    "/api/*": Response.json({ message: "No encontrado" }, { status: 404 }),

    // Redirección de /blog/hello a /blog/hello/world
    "/blog/hello": Response.redirect("/blog/hello/world"),

    // Servir un archivo cargándolo perezosamente en memoria
    "/favicon.ico": Bun.file("./favicon.ico"),
  },

  // (opcional) fallback para rutas no coincidentes:
  // Requerido si la versión de Bun < 1.2.3
  fetch(req) {
    return new Response("No encontrado", { status: 404 });
  },
});

console.log(`Servidor ejecutándose en ${server.url}`);

Importaciones HTML

Bun soporta importar archivos HTML directamente en tu código de servidor, permitiendo aplicaciones full-stack con código tanto del lado del servidor como del cliente. Las importaciones HTML funcionan en dos modos:

Desarrollo (bun --hot): Los recursos se empaquetan bajo demanda en tiempo de ejecución, habilitando reemplazo de módulos en caliente (HMR) para una experiencia de desarrollo rápida e iterativa. Cuando cambias tu código frontend, el navegador se actualiza automáticamente sin una recarga completa de página.

Producción (bun build): Cuando construyes con bun build --target=bun, la declaración import index from "./index.html" se resuelve a un objeto manifiesto precompilado que contiene todos los recursos del cliente empaquetados. Bun.serve consume este manifiesto para servir recursos optimizados con cero sobrecarga de empaquetado en tiempo de ejecución. Esto es ideal para desplegar a producción.

ts
import myReactSinglePageApp from "./index.html";

Bun.serve({
  routes: {
    "/": myReactSinglePageApp,
  },
});

Las importaciones HTML no solo sirven HTML — es un empaquetador frontend completo, transpilador y conjunto de herramientas construido usando el bundler de Bun, transpilador de JavaScript y analizador CSS. Puedes usar esto para construir frontends completos con React, TypeScript, Tailwind CSS y más.

Para una guía completa sobre construir aplicaciones full-stack con importaciones HTML, incluyendo ejemplos detallados y mejores prácticas, consulta /docs/bundler/fullstack.


Configuración

Cambiar el port y hostname

Para configurar en qué puerto y hostname escuchará el servidor, establece port y hostname en el objeto de opciones.

ts
Bun.serve({
  port: 8080, // predeterminado a $BUN_PORT, $PORT, $NODE_PORT de lo contrario 3000
  hostname: "mydomain.com", // predeterminado a "0.0.0.0"
  fetch(req) {
    return new Response("¡404!");
  },
});

Para seleccionar aleatoriamente un puerto disponible, establece port en 0.

ts
const server = Bun.serve({
  port: 0, // puerto aleatorio
  fetch(req) {
    return new Response("¡404!");
  },
});

// server.port es el puerto seleccionado aleatoriamente
console.log(server.port);

Puedes ver el puerto seleccionado accediendo a la propiedad port en el objeto del servidor, o accediendo a la propiedad url.

ts
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000

Configurar un puerto predeterminado

Bun soporta varias opciones y variables de entorno para configurar el puerto predeterminado. El puerto predeterminado se usa cuando la opción port no está establecida.

  • Flag del CLI --port
sh
bun --port=4002 server.ts
  • Variable de entorno BUN_PORT
sh
BUN_PORT=4002 bun server.ts
  • Variable de entorno PORT
sh
PORT=4002 bun server.ts
  • Variable de entorno NODE_PORT
sh
NODE_PORT=4002 bun server.ts

Sockets de dominio Unix

Para escuchar en un socket de dominio unix, pasa la opción unix con la ruta al socket.

ts
Bun.serve({
  unix: "/tmp/my-socket.sock", // ruta al socket
  fetch(req) {
    return new Response(`¡404!`);
  },
});

Sockets de espacio de nombres abstracto

Bun soporta sockets de espacio de nombres abstracto de Linux. Para usar un socket de espacio de nombres abstracto, prefija la ruta unix con un byte nulo.

ts
Bun.serve({
  unix: "\0my-abstract-socket", // socket de espacio de nombres abstracto
  fetch(req) {
    return new Response(`¡404!`);
  },
});

A diferencia de los sockets de dominio unix, los sockets de espacio de nombres abstracto no están vinculados al sistema de archivos y se eliminan automáticamente cuando se cierra la última referencia al socket.


idleTimeout

Para configurar el tiempo de espera de inactividad, establece el campo idleTimeout en Bun.serve.

ts
Bun.serve({
  // 10 segundos:
  idleTimeout: 10,

  fetch(req) {
    return new Response("¡Bun!");
  },
});

Esta es la cantidad máxima de tiempo que se permite que una conexión esté inactiva antes de que el servidor la cierre. Una conexión está inactiva si no hay datos enviados o recibidos.


Sintaxis export default

Hasta ahora, los ejemplos en esta página han usado la API explícita Bun.serve. Bun también soporta una sintaxis alternativa.

ts
import { type Serve } from "bun";

export default {
  fetch(req) {
    return new Response("¡Bun!");
  },
} satisfies Serve;

En lugar de pasar las opciones del servidor a Bun.serve, haz export default de ellas. Este archivo se puede ejecutar tal cual; cuando Bun ve un archivo con una exportación default que contiene un manejador fetch, lo pasa a Bun.serve internamente.


Recarga en Caliente de Rutas

Actualiza rutas sin reinicios del servidor usando server.reload():

ts
const server = Bun.serve({
  routes: {
    "/api/version": () => Response.json({ version: "1.0.0" }),
  },
});

// Desplegar nuevas rutas sin tiempo de inactividad
server.reload({
  routes: {
    "/api/version": () => Response.json({ version: "2.0.0" }),
  },
});

Métodos del Ciclo de Vida del Servidor

server.stop()

Para detener el servidor de aceptar nuevas conexiones:

ts
const server = Bun.serve({
  fetch(req) {
    return new Response("¡Hola!");
  },
});

// Detener gracefulmente el servidor (espera solicitudes en vuelo)
await server.stop();

// Forzar detención y cerrar todas las conexiones activas
await server.stop(true);

Por defecto, stop() permite que las solicitudes en vuelo y las conexiones WebSocket se completen. Pasa true para terminar inmediatamente todas las conexiones.

server.ref() y server.unref()

Controla si el servidor mantiene vivo el proceso de Bun:

ts
// No mantener el proceso vivo si el servidor es lo único ejecutándose
server.unref();

// Restaurar comportamiento predeterminado - mantener el proceso vivo
server.ref();

server.reload()

Actualiza los manejadores del servidor sin reiniciar:

ts
const server = Bun.serve({
  routes: {
    "/api/version": Response.json({ version: "v1" }),
  },
  fetch(req) {
    return new Response("v1");
  },
});

// Actualizar a nuevo manejador
server.reload({
  routes: {
    "/api/version": Response.json({ version: "v2" }),
  },
  fetch(req) {
    return new Response("v2");
  },
});

Esto es útil para desarrollo y recarga en caliente. Solo se pueden actualizar fetch, error y routes.


Controles Por Solicitud

server.timeout(Request, seconds)

Establece un tiempo de espera de inactividad personalizado para solicitudes individuales:

ts
const server = Bun.serve({
  async fetch(req, server) {
    // Establecer tiempo de espera de 60 segundos para esta solicitud
    server.timeout(req, 60);

    // Si tardan más de 60 segundos en enviar el cuerpo, la solicitud se abortará
    await req.text();

    return new Response("¡Hecho!");
  },
});

Pasa 0 para deshabilitar el tiempo de espera para una solicitud.

server.requestIP(Request)

Obtén información de IP y puerto del cliente:

ts
const server = Bun.serve({
  fetch(req, server) {
    const address = server.requestIP(req);
    if (address) {
      return new Response(`IP del cliente: ${address.address}, Puerto: ${address.port}`);
    }
    return new Response("Cliente desconocido");
  },
});

Devuelve null para solicitudes cerradas o sockets de dominio Unix.


Métricas del Servidor

server.pendingRequests y server.pendingWebSockets

Monitorea la actividad del servidor con contadores integrados:

ts
const server = Bun.serve({
  fetch(req, server) {
    return new Response(
      `Solicitudes activas: ${server.pendingRequests}\n` + `WebSockets activos: ${server.pendingWebSockets}`,
    );
  },
});

server.subscriberCount(topic)

Obtén el conteo de suscriptores para un tema WebSocket:

ts
const server = Bun.serve({
  fetch(req, server) {
    const chatUsers = server.subscriberCount("chat");
    return new Response(`${chatUsers} usuarios en el chat`);
  },
  websocket: {
    message(ws) {
      ws.subscribe("chat");
    },
  },
});

Benchmarks

A continuación hay implementaciones de Bun y Node.js de un servidor HTTP simple que responde ¡Bun! a cada Request entrante.

ts
Bun.serve({
  fetch(req: Request) {
    return new Response("¡Bun!");
  },
  port: 3000,
});
ts
require("http")
  .createServer((req, res) => res.end("¡Bun!"))
  .listen(8080);

El servidor Bun.serve puede manejar aproximadamente 2.5x más solicitudes por segundo que Node.js en Linux.

RuntimeSolicitudes por segundo
Node 16~64,000
Bun~160,000

Ejemplo Práctico: API REST

Aquí hay una API REST básica respaldada por base de datos usando el enrutador de Bun con cero dependencias:

ts
import type { Post } from "./types.ts";
import { Database } from "bun:sqlite";

const db = new Database("posts.db");
db.exec(`
  CREATE TABLE IF NOT EXISTS posts (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    created_at TEXT NOT NULL
  )
`);

Bun.serve({
  routes: {
    // Listar posts
    "/api/posts": {
      GET: () => {
        const posts = db.query("SELECT * FROM posts").all();
        return Response.json(posts);
      },

      // Crear post
      POST: async req => {
        const post: Omit<Post, "id" | "created_at"> = await req.json();
        const id = crypto.randomUUID();

        db.query(
          `INSERT INTO posts (id, title, content, created_at)
           VALUES (?, ?, ?, ?)`,
        ).run(id, post.title, post.content, new Date().toISOString());

        return Response.json({ id, ...post }, { status: 201 });
      },
    },

    // Obtener post por ID
    "/api/posts/:id": req => {
      const post = db.query("SELECT * FROM posts WHERE id = ?").get(req.params.id);

      if (!post) {
        return new Response("No encontrado", { status: 404 });
      }

      return Response.json(post);
    },
  },

  error(error) {
    console.error(error);
    return new Response("Error interno del servidor", { status: 500 });
  },
});
ts
export interface Post {
  id: string;
  title: string;
  content: string;
  created_at: string;
}

Referencia

ts
interface Server extends Disposable {
  /**
   * Detener el servidor de aceptar nuevas conexiones.
   * @param closeActiveConnections Si es true, termina inmediatamente todas las conexiones
   * @returns Promesa que se resuelve cuando el servidor se ha detenido
   */
  stop(closeActiveConnections?: boolean): Promise<void>;

  /**
   * Actualizar manejadores sin reiniciar el servidor.
   * Solo se pueden actualizar los manejadores fetch y error.
   */
  reload(options: Serve): void;

  /**
   * Hacer una solicitud al servidor en ejecución.
   * Útil para pruebas o enrutamiento interno.
   */
  fetch(request: Request | string): Response | Promise<Response>;

  /**
   * Actualizar una solicitud HTTP a una conexión WebSocket.
   * @returns true si la actualización es exitosa, false si falló
   */
  upgrade<T = undefined>(
    request: Request,
    options?: {
      headers?: Bun.HeadersInit;
      data?: T;
    },
  ): boolean;

  /**
   * Publicar un mensaje a todos los clientes WebSocket suscritos a un tema.
   * @returns Bytes enviados, 0 si se descartó, -1 si se aplicó contrapresión
   */
  publish(
    topic: string,
    data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
    compress?: boolean,
  ): ServerWebSocketSendStatus;

  /**
   * Obtener conteo de clientes WebSocket suscritos a un tema.
   */
  subscriberCount(topic: string): number;

  /**
   * Obtener dirección IP y puerto del cliente.
   * @returns null para solicitudes cerradas o sockets Unix
   */
  requestIP(request: Request): SocketAddress | null;

  /**
   * Establecer tiempo de espera de inactividad personalizado para una solicitud.
   * @param seconds Tiempo de espera en segundos, 0 para deshabilitar
   */
  timeout(request: Request, seconds: number): void;

  /**
   * Mantener el proceso vivo mientras el servidor está en ejecución.
   */
  ref(): void;

  /**
   * Permitir que el proceso termine si el servidor es lo único ejecutándose.
   */
  unref(): void;

  /** Número de solicitudes HTTP en vuelo */
  readonly pendingRequests: number;

  /** Número de conexiones WebSocket activas */
  readonly pendingWebSockets: number;

  /** URL del servidor incluyendo protocolo, hostname y puerto */
  readonly url: URL;

  /** Puerto en el que el servidor está escuchando */
  readonly port: number;

  /** Hostname al que el servidor está vinculado */
  readonly hostname: string;

  /** Si el servidor está en modo desarrollo */
  readonly development: boolean;

  /** Identificador de instancia del servidor */
  readonly id: string;
}

interface WebSocketHandler<T = undefined> {
  /** Tamaño máximo de mensaje WebSocket en bytes */
  maxPayloadLength?: number;

  /** Bytes de mensajes en cola antes de aplicar contrapresión */
  backpressureLimit?: number;

  /** Si cerrar la conexión cuando se alcanza el límite de contrapresión */
  closeOnBackpressureLimit?: boolean;

  /** Llamado cuando se alivia la contrapresión */
  drain?(ws: ServerWebSocket<T>): void | Promise<void>;

  /** Segundos antes del tiempo de espera de inactividad */
  idleTimeout?: number;

  /** Habilitar compresión deflate por mensaje */
  perMessageDeflate?:
    | boolean
    | {
        compress?: WebSocketCompressor | boolean;
        decompress?: WebSocketCompressor | boolean;
      };

  /** Enviar frames ping para mantener la conexión viva */
  sendPings?: boolean;

  /** Si el servidor recibe sus propios mensajes publicados */
  publishToSelf?: boolean;

  /** Llamado cuando se abre la conexión */
  open?(ws: ServerWebSocket<T>): void | Promise<void>;

  /** Llamado cuando se recibe un mensaje */
  message(ws: ServerWebSocket<T>, message: string | Buffer): void | Promise<void>;

  /** Llamado cuando se cierra la conexión */
  close?(ws: ServerWebSocket<T>, code: number, reason: string): void | Promise<void>;

  /** Llamado cuando se recibe un frame ping */
  ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;

  /** Llamado cuando se recibe un frame pong */
  pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
}

interface TLSOptions {
  /** Cadena de autoridad de certificado */
  ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** Certificado del servidor */
  cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** Ruta al archivo de parámetros DH */
  dhParamsFile?: string;

  /** Clave privada */
  key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;

  /** Reducir uso de memoria TLS */
  lowMemoryMode?: boolean;

  /** Frase de contraseña de clave privada */
  passphrase?: string;

  /** Banderas de opciones de OpenSSL */
  secureOptions?: number;

  /** Nombre del servidor para SNI */
  serverName?: string;
}

Bun por www.bunjs.com.cn editar