Qué es Bun
Bun es un runtime de JavaScript y TypeScript construido desde cero en Zig y JavaScriptCore (el motor de Safari). A diferencia de Node.js, Bun ejecuta archivos .ts directamente sin necesidad de un paso de compilación previo, eliminando la configuración de tsc, ts-node o transpiladores externos.

Bun integra en un único binario cuatro herramientas que tradicionalmente requieren paquetes separados:
- Runtime: ejecuta JavaScript y TypeScript nativamente
- Gestor de paquetes: alternativa a npm, yarn o pnpm
- Bundler: empaquetado de módulos para producción
- Test runner: ejecución de tests con API compatible con Jest
// archivo: hola.ts
const mensaje: string = "Hola desde Bun"
console.log(mensaje)
bun run hola.ts
# Hola desde Bun
Bun transpila TypeScript internamente pero no realiza verificación de tipos. Para validación de tipos se sigue necesitando
tsc --noEmito el soporte del editor. Bun prioriza velocidad de ejecución sobre comprobación estática.
Instalación y configuración
Bun se instala mediante un script oficial o a través de gestores de paquetes del sistema:
# Linux y macOS
curl -fsSL https://bun.sh/install | bash
# Windows (PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"
# Verificar instalación
bun --versión
Para inicializar un proyecto TypeScript con Bun:
bun init
Este comando genera un package.json, un tsconfig.json preconfigurado y un archivo de entrada index.ts. El tsconfig.json generado por Bun incluye ajustes optimizados:
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"composite": true,
"strict": true,
"skipLibCheck": true
}
}
La opción moduleResolution: "bundler" es clave: permite imports sin extensión y resuelve exports de package.json correctamente.
Ejecutar TypeScript directamente
La ventaja principal de Bun es la ejecución directa de archivos .ts sin compilación:
// archivo: servidor.ts
interface Ruta {
patrón: string
manejador: (req: Request) => Response
}
const rutas: Ruta[] = [
{
patrón: "/",
manejador: () => new Response("Pagina principal")
},
{
patrón: "/api/estado",
manejador: () => Response.json({ estado: "activo", timestamp: Date.now() })
}
]
const servidor = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url)
const ruta = rutas.find(r => r.patrón === url.pathname)
if (ruta) {
return ruta.manejador(req)
}
return new Response("No encontrado", { status: 404 })
}
})
console.log(`Servidor escuchando en http://localhost:${servidor.port}`)
bun run servidor.ts
Bun también permite ejecutar scripts definidos en package.json:
{
"scripts": {
"dev": "bun run --watch servidor.ts",
"start": "bun run servidor.ts"
}
}
La flag --watch reinicia automáticamente el proceso cuando detecta cambios en los archivos fuente.
Lectura y escritura de archivos
Bun proporciona APIs propias optimizadas para operaciones de E/S:
// archivo: archivos.ts
// Escribir archivo
await Bun.write("datos.json", JSON.stringify({ nombre: "Ana", edad: 30 }))
// Leer archivo
const contenido = await Bun.file("datos.json").text()
const datos = JSON.parse(contenido)
console.log(datos.nombre) // Ana
// Leer como JSON directamente
const datosJSON = await Bun.file("datos.json").json()
console.log(datosJSON.edad) // 30
// Verificar existencia
const existe = await Bun.file("datos.json").exists()
console.log(`Archivo existe: ${existe}`)
Test runner integrado
Bun incluye un test runner con una API similar a Jest que funciona directamente con archivos TypeScript:
// archivo: calculadora.ts
export function sumar(a: number, b: number): number {
return a + b
}
export function dividir(a: number, b: number): number {
if (b === 0) {
throw new Error("Division por cero")
}
return a / b
}
export function esPar(n: number): boolean {
return n % 2 === 0
}
// archivo: calculadora.test.ts
import { describe, expect, test } from "bun:test"
import { sumar, dividir, esPar } from "./calculadora"
describe("sumar", () => {
test("suma dos números positivos", () => {
expect(sumar(2, 3)).toBe(5)
})
test("suma con números negativos", () => {
expect(sumar(-1, 1)).toBe(0)
})
test("suma con decimales", () => {
expect(sumar(0.1, 0.2)).toBeCloseTo(0.3)
})
})
describe("dividir", () => {
test("divide dos números", () => {
expect(dividir(10, 2)).toBe(5)
})
test("lanza error al dividir por cero", () => {
expect(() => dividir(10, 0)).toThrow("Division por cero")
})
})
describe("esPar", () => {
test("detecta números pares", () => {
expect(esPar(4)).toBe(true)
expect(esPar(0)).toBe(true)
})
test("detecta números impares", () => {
expect(esPar(3)).toBe(false)
expect(esPar(-1)).toBe(false)
})
})
bun test
# Ejecuta todos los archivos *.test.ts
bun test calculadora.test.ts
# Ejecuta un archivo específico
bun test --watch
# Re-ejecuta tests al detectar cambios
El test runner de Bun es compatible con la mayoría de matchers de Jest (
toBe,toEqual,toThrow,toBeCloseTo, etc.), lo que facilita la migración desde proyectos existentes.
Gestión de paquetes
El gestor de paquetes de Bun es compatible con package.json y node_modules, pero significativamente más rápido que npm o yarn:
# Instalar dependencias
bun install
# Anadir paquete
bun add zod
# Anadir como dependencia de desarrollo
bun add -d @types/node
# Eliminar paquete
bun remove zod
# Actualizar paquetes
bun update
Bun genera un archivo bun.lockb (formato binario) en lugar de package-lock.json. Este formato binario es más rápido de leer y escribir:
// archivo: validación.ts
import { z } from "zod"
const EsquemaUsuario = z.object({
nombre: z.string().min(2),
email: z.string().email(),
edad: z.number().int().positive()
})
type Usuario = z.infer<typeof EsquemaUsuario>
function validarUsuario(datos: unknown): Usuario {
return EsquemaUsuario.parse(datos)
}
try {
const usuario = validarUsuario({
nombre: "Ana",
email: "ana@ejemplo.com",
edad: 28
})
console.log(`Usuario válido: ${usuario.nombre}`)
} catch (error) {
console.error("Datos invalidos:", error)
}
Bundling con bun build
Bun incluye un bundler para empaquetar código TypeScript en archivos JavaScript optimizados para producción:
bun build ./entrada.ts --outdir ./dist
// archivo: entrada.ts
import { sumar, dividir } from "./calculadora"
const resultado = sumar(10, 20)
console.log(`Suma: ${resultado}`)
try {
console.log(`Division: ${dividir(100, 4)}`)
} catch (error) {
console.error((error as Error).message)
}
# Bundle básico
bun build ./entrada.ts --outdir ./dist
# Bundle minificado
bun build ./entrada.ts --outdir ./dist --minify
# Bundle con target específico
bun build ./entrada.ts --outdir ./dist --target bun
bun build ./entrada.ts --outdir ./dist --target browser
bun build ./entrada.ts --outdir ./dist --target node
También se puede usar la API programatica:
// archivo: build.ts
const resultado = await Bun.build({
entrypoints: ["./entrada.ts"],
outdir: "./dist",
minify: true,
target: "bun"
})
if (resultado.success) {
console.log("Bundle generado correctamente")
for (const output of resultado.outputs) {
console.log(` ${output.path} (${output.size} bytes)`)
}
} else {
console.error("Errores durante el bundling:")
for (const log of resultado.logs) {
console.error(` ${log.message}`)
}
}
APIs propias del runtime
Además de implementar las APIs de Node.js y los estándares web (Fetch, Request, Response, ReadableStream), Bun expone utilidades propias bajo el objeto global Bun que aceleran tareas habituales.
Hashing y contraseñas
Bun.password ofrece hashing de contraseñas con bcrypt o argon2 sin dependencias externas:
const hash = await Bun.password.hash("contraseña-secreta", {
algorithm: "argon2id",
memoryCost: 65536,
timeCost: 2
})
const valida = await Bun.password.verify("contraseña-secreta", hash)
console.log(`Contraseña válida: ${valida}`)
Bun.hash genera hashes no criptográficos muy rápidos útiles para claves de caché, checksums o particionado:
const clave = Bun.hash("usuario:42:sesion")
console.log(clave.toString(16))
Ejecución de subprocesos
Bun.spawn simplifica la ejecución de procesos externos con una API moderna basada en promesas y streams:
const proceso = Bun.spawn(["git", "status", "--short"], {
stdout: "pipe"
})
const salida = await new Response(proceso.stdout).text()
console.log(salida)
const codigo = await proceso.exited
console.log(`Código de salida: ${codigo}`)
Variables de entorno sin dotenv
Bun lee automáticamente los archivos .env, .env.local, .env.development y .env.production según NODE_ENV, exponiendo los valores en process.env y en Bun.env.
# .env
DATABASE_URL=postgres://localhost/miapp
API_KEY=secreto
// no hace falta importar dotenv
const db = Bun.env.DATABASE_URL
console.log(`Conectando a ${db}`)
Cliente SQLite y PostgreSQL integrado
Bun incluye clientes nativos para SQLite (bun:sqlite) y, desde versiones recientes, un cliente SQL integrado para Postgres:
import { Database } from "bun:sqlite"
const db = new Database("app.db")
db.run(`
CREATE TABLE IF NOT EXISTS usuarios (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL
)
`)
const insertar = db.prepare("INSERT INTO usuarios (nombre) VALUES (?)")
insertar.run("Ana")
insertar.run("Luis")
const consulta = db.query("SELECT id, nombre FROM usuarios")
console.log(consulta.all())
Workspaces y monorepos
Bun soporta de forma nativa el formato workspaces de package.json, muy usado en monorepos con varios paquetes:
{
"name": "mi-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
]
}
Con esta configuración, bun install instala dependencias compartidas una sola vez en la raíz y enlaza los paquetes locales entre sí. bun run --cwd apps/web dev ejecuta un script en un workspace concreto sin cambiar de directorio.
Esta capacidad integrada evita herramientas adicionales como Turborepo o Lerna para casos de uso simples, aunque se puede combinar con ellas si se necesita cacheo avanzado de builds.
Despliegue en producción
Para producción, la recomendación general es generar un bundle con bun build --minify --target bun o --target node según el entorno de destino, y ejecutar el resultado con el runtime correspondiente. Bun también ofrece la capacidad de compilar a un único ejecutable independiente con --compile:
bun build ./src/servidor.ts --compile --outfile ./dist/servidor
./dist/servidor
El binario resultante no requiere que Bun esté instalado en la máquina de destino, lo que facilita el despliegue en contenedores minimalistas y en entornos serverless que soportan binarios nativos.
En Docker, un Dockerfile típico para una aplicación Bun se parece a:
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY . .
RUN bun build ./src/index.ts --outdir ./dist --target bun --minify
FROM oven/bun:1-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["bun", "run", "dist/index.js"]
La imagen oven/bun:1-slim reduce el tamaño final sustancialmente respecto a la imagen completa y es suficiente para ejecutar aplicaciones ya empaquetadas.
Comparación con Node.js + tsc
La principal diferencia de flujo de trabajo entre Bun y el stack tradicional de Node.js:
| Aspecto | Node.js + tsc | Bun |
|---|---|---|
| Ejecutar .ts | Requiere tsc o ts-node | Directo: bun run archivo.ts |
| Instalar paquetes | npm install | bun install (más rápido) |
| Tests | Jest/Vitest (configuración externa) | bun test (integrado) |
| Bundling | webpack/esbuild/rollup | bun build (integrado) |
| Verificación de tipos | tsc --noEmit | tsc --noEmit (igual) |
| Watch mode | nodemon o ts-node --watch | bun run --watch |
// El mismo código funciona en ambos runtimes
interface Tarea {
id: number
titulo: string
completada: boolean
}
const tareas: Tarea[] = [
{ id: 1, titulo: "Aprender TypeScript", completada: true },
{ id: 2, titulo: "Probar Bun", completada: false },
{ id: 3, titulo: "Crear proyecto", completada: false }
]
const pendientes = tareas.filter(t => !t.completada)
console.log(`Tareas pendientes: ${pendientes.length}`)
for (const tarea of pendientes) {
console.log(` - ${tarea.titulo}`)
}
# Con Node.js (requiere ts-node o compilación previa)
npx ts-node tareas.ts
# Con Bun (directo)
bun run tareas.ts
Bun es compatible con la gran mayoría de paquetes de npm y APIs de Node.js. Sin embargo, algunos módulos nativos de Node.js pueden no estar completamente soportados. Es recomendable verificar la compatibilidad al migrar proyectos existentes.
Bun simplifica el flujo de trabajo con TypeScript al eliminar la necesidad de configurar y coordinar múltiples herramientas. Para proyectos nuevos donde la velocidad de desarrollo es prioritaria, Bun ofrece una experiencia integrada que reduce significativamente la fricción de configuración inicial.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de TypeScript
Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Instalar y configurar Bun como runtime nativo para TypeScript. Ejecutar archivos .ts directamente sin paso de compilación. Utilizar el test runner integrado de Bun. Gestionar paquetes con bun install y crear bundles con bun build.