Bun: runtime, gestor de paquetes y test runner en un binario
Bun es un único binario que corre TypeScript sin transpilar aparte, instala paquetes de npm hasta 30 veces más rápido y levanta servidores HTTP con las mismas APIs Request y Response del navegador. Sirve para dejar de armar tu stack con cuatro herramientas distintas.

Un binario reemplaza cuatro herramientas separadas
Bun no es solo un runtime alternativo a Node: el mismo comando bun hace de runtime, gestor de paquetes, test runner y bundler. Eso significa que el mismo binario que ejecuta tu servidor también instala dependencias, corre tests con API compatible con Jest y bundlea para el browser. La ventaja concreta es menos piezas que versionar y actualizar en tu proyecto: no hay ts-node, no hay Jest aparte, no hay webpack o esbuild como dependencia extra.
bun run start # corre el script "start" del package.jsonbun install <pkg> # instala una dependenciabun build ./index.tsx # bundlea un proyecto para el browserbun test # corre los testsbunx cowsay 'Hello, world!' # ejecuta un paquete sin instalarloTypeScript y JSX corren directo, sin paso de compilación previo
Bun está escrito en Rust y usa JavaScriptCore (el motor de Safari) en vez de V8, lo que reduce el tiempo de arranque y el uso de memoria frente a Node. La consecuencia práctica es que podés ejecutar un archivo .ts o .tsx tal cual, sin tsc ni un loader configurado: Bun transpila internamente antes de correr el código. Esto elimina una capa entera de configuración que en un proyecto Node típico vive en tsconfig.json más un bundler.
// index.tsconst greet = (name: string): string => `Hello, ${name}, from Bun ${Bun.version}!`;console.log(greet("AI Talks"));// $ bun run index.ts// Hello, AI Talks, from Bun 1.x.x!Bun.serve() usa Request y Response, no una API propia
El servidor HTTP nativo de Bun se expone como Bun.serve() y recibe un handler fetch que trabaja con los objetos Request y Response estándar de la web, los mismos que fetch() en el browser. Esto importa porque el mismo handler es portable a otros runtimes que comparten ese contrato, sin reescribir nada. Corriendo el archivo con la flag --hot, Bun recarga el módulo en cada guardado pero preserva el estado en memoria, así una conexión abierta o un caché no se pierden en cada edición.
// src/server.tsconst server = Bun.serve({ port: 3000, hostname: "0.0.0.0", fetch(req) { const url = new URL(req.url); if (url.pathname === "/") { return new Response("Bun is alive on " + Bun.version); } if (url.pathname === "/health") { return Response.json({ status: "ok", uptime: process.uptime() }); } return new Response("Not found", { status: 404 }); }, error(err) { console.error(err); return new Response("Internal Server Error", { status: 500 }); },});console.log(`Listening on http://${server.hostname}:${server.port}`);bun:sqlite trae la base de datos embebida en el binario
Bun incluye un binding a SQLite bajo el módulo bun:sqlite, sin instalar nada ni compilar un módulo nativo como better-sqlite3. Esto sirve para tener persistencia real desde el primer minuto de un proyecto chico, sin levantar un servidor de base de datos aparte. La opción strict: true, más nueva en Bun, tira error si mandás un parámetro del tipo equivocado en vez de coaccionarlo en silencio, y el modo WAL evita que las escrituras bloqueen las lecturas en un servidor con tráfico concurrente.
// src/db.tsimport { Database } from "bun:sqlite";const dbPath = process.env.DB_PATH ?? "data.db";export const db = new Database(dbPath, { create: true, strict: true });db.exec("PRAGMA journal_mode = WAL;");db.exec("PRAGMA foreign_keys = ON;");db.exec(` CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, body TEXT NOT NULL, published INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP );`);bun install con caché global y control explícito de scripts
El gestor de paquetes de Bun resuelve dependencias contra un caché global en ~/.bun/install/cache y usa hard links en Linux y macOS, así que instalar el mismo paquete en dos proyectos distintos casi no ocupa disco extra. Para reproducibilidad en CI existe bun install --frozen-lockfile, que falla si el lockfile no coincide exactamente con lo instalado. Por seguridad, Bun solo corre scripts postinstall de paquetes que estén listados explícitamente en trustedDependencies dentro del package.json, así un paquete nativo no ejecuta código arbitrario en la instalación sin que vos lo hayas autorizado.
bun add hono zodbun add -d @types/node typescriptbun pm lsbun install --frozen-lockfileQué puedes construir con esto
Tres proyectos que se hacen en una tarde y terminan en algo que corre.
API de tareas con persistencia en SQLite, sin ORM ni Docker
Un servidor Bun.serve() con rutas para crear y listar tareas guardadas en una tabla de bun:sqlite.
Servidor con hot reload que no pierde estado en memoria
Un server Bun.serve() con un contador o caché en memoria, corrido con la flag --hot mientras editás el handler.
Migrar un proyecto Node a Bun y medir la instalación con datos propios
Reemplazar npm install por bun install en un repo existente y comparar tiempos con bun pm ls y bun install --frozen-lockfile.
Qué te llevas
Bun junta runtime, gestor de paquetes, test runner y bundler en un solo binario: menos herramientas externas que mantener en tu proyecto.
Bun.serve() trabaja con Request y Response estándar de la web, así que el mismo handler es portable a otros runtimes sin reescribirlo.
bun:sqlite te da persistencia embebida sin instalar módulos nativos, útil para prototipos que necesitan guardar datos de verdad desde el día uno.
El caché global y trustedDependencies hacen que bun install sea rápido y explícito sobre qué scripts postinstall se ejecutan, en vez de correrlos todos en silencio.