Volver al blog
BunJavaScriptNode.jsArquitectura

Bun en un proyecto largo: la lección de la ciudad de Minecraft

04 de octubre de 2026·7 min de lectura·Diego Horvatti

Mike Tomlin, entrenador de la NFL, pasó 12 años construyendo una ciudad en Minecraft. Doce años. Bloque a bloque. La historia salió en The Athletic y la leí pensando en algo que no tiene nada que ver con el fútbol americano: cómo adoptamos herramientas como Bun en proyectos que van a vivir mucho tiempo.

¿Suena forzado? Un poco. Pero acompáñame.

Qué tiene que ver un entrenador de la NFL con tu backend

Nadie construye una ciudad de 12 años en un fin de semana. Pones una calle. Luego una casa. Luego te das cuenta de que la calle quedó torcida y rehaces solo ese tramo. La ciudad nunca deja de funcionar mientras la tocas.

Un proyecto de software que dura es exactamente igual. El sistema que mantienes hoy probablemente tiene código de tres o cuatro "eras" distintas. Hay una parte en CommonJS y otra en ESM. Hay Jest y hay un script de shell que nadie recuerda quién escribió. Y hay gente usándolo en producción ahora mismo, mientras lees esto.

Es en ese escenario donde aparecen las ganas de cambiarlo todo por Bun. Es rápido, ejecuta TypeScript directamente y ya trae test runner, bundler, gestor de paquetes, SQLite y cliente de Postgres. La tentación de hacer un "big bang" es enorme.

No lo hagas.

Por qué reescribirlo todo en Bun es la peor estrategia

La reescritura completa tiene un problema conocido: durante semanas tienes dos sistemas y ninguno está terminado. El antiguo sigue recibiendo correcciones. El nuevo siempre va por detrás. Cuando por fin haces el cambio, aparece ese bug que solo ocurría con una combinación rara de dependencia nativa y variable de entorno.

Y Bun tiene un detalle que pesa aquí: la compatibilidad con Node es muy buena, pero no es del 100%. Paquetes con addons nativos, cosas que dependen de un comportamiento específico de los módulos internos node:, herramientas que leen process.versions y deciden el camino a partir de eso. En la mayoría de los proyectos no pasa nada. Pero "en la mayoría" no es "en el tuyo".

Nadie construye una ciudad entera de una vez. Ni migra un backend.

Lo sensato es hacerlo como Tomlin: un bloque cada vez, con la ciudad funcionando todo el tiempo.

Cómo adoptar Bun bloque a bloque

Este es el orden que uso y recomiendo. Cada paso es reversible. Si algo falla, vuelves un paso atrás y sigues con tu vida.

Bloque 1: solo el gestor de paquetes

El paso más barato. Cambias npm install por bun install y mantienes Node como runtime.

rm -rf node_modules package-lock.json
bun install

Bun genera un bun.lock en texto, que se puede revisar en un PR sin sufrir. En el CI, el install suele bajar de forma bastante notable, sobre todo en monorepos con muchas dependencias. Tu código no cambió ni una línea. Si algo sale mal, restauras el lockfile anterior y listo.

Bloque 2: los tests

bun test acepta la API al estilo Jest: describe, it, expect, mock. En buena parte de los casos, los tests corren sin cambios.

import { describe, it, expect } from 'bun:test'
import { calcularFrete } from './frete'

describe('calcularFrete', () => {
  it('envío gratis por encima de 300 reales', () => {
    expect(calcularFrete({ total: 350, cep: '01001000' })).toBe(0)
  })
})

Aquí ya notas la diferencia en el día a día. Un test que tarda en arrancar es un test que nadie ejecuta antes del commit. Un test que corre rápido se vuelve hábito.

Un consejo: empieza por una sola carpeta. Ejecuta bun test src/utils y mira qué se rompe. Normalmente es un mock de módulo o algún jest.useFakeTimers más exótico. Lo resuelves y amplías.

Bloque 3: scripts y herramientas internas

¿Conoces ese script de seed de la base de datos? ¿El que migra datos? ¿El que genera informes? Son candidatos perfectos. Nadie accede desde fuera, un fallo no tumba producción, y la ventaja de ejecutar .ts directamente sin ts-node ni build es inmediata.

// scripts/seed.ts
import { sql } from 'bun'

const usuarios = await Bun.file('./fixtures/usuarios.json').json()

for (const u of usuarios) {
  await sql`insert into usuarios (nome, email) values (${u.nome}, ${u.email})`
}

console.log(`${usuarios.length} usuarios insertados`)

Se ejecuta con bun scripts/seed.ts. Sin configuración. Sin dependencias nuevas.

Bloque 4: un servicio nuevo y pequeño

Solo ahora Bun se convierte en el runtime de algo en producción. Y no es tu monolito. Es un servicio nuevo, de alcance cerrado: un webhook, un worker de cola, una API interna.

Bun.serve({
  port: 3000,
  routes: {
    '/health': new Response('ok'),
    '/webhook': {
      POST: async (req) => {
        const evento = await req.json()
        await processar(evento)
        return new Response(null, { status: 204 })
      },
    },
  },
})

Si ese servicio pasa algunos meses sin sorpresas, entonces sí tienes datos reales para discutir cambiar el runtime del resto.

Bloque 5: el resto, si tiene sentido

Fíjate en el "si". Hay proyectos que se quedan en el bloque 2 y están perfectos. Una instalación rápida y tests rápidos ya compensan el esfuerzo. No hay medalla por usar Bun en todo.

Qué revisar antes de poner Bun en producción

Algunos puntos que reviso antes de subir un servicio con Bun:

  • Dependencias nativas. Ejecuta bun install y mira si algún paquete se queja del build. bcrypt, sharp y los drivers de base de datos antiguos son los sospechosos de siempre. Para el hash de contraseñas, Bun.password lo resuelve sin paquetes extra.
  • Imagen Docker. Usa la imagen oficial oven/bun y fija la versión. "latest" en producción es buscarse una sorpresa un viernes por la tarde.
  • Observabilidad. Comprueba que tu APM y tu logger funcionan en Bun. Algunos agentes instrumentan a nivel del runtime de Node y simplemente no ven nada.
  • Plataforma de deploy. Hoy varias plataformas ya aceptan Bun como runtime. Aun así, prueba el deploy en un entorno de preview antes.

Nada de esto es motivo para no usarlo. Es solo el equivalente a mirar el terreno antes de poner el primer bloque.

¿Y la lección de 12 años de Minecraft?

Imagino que la ciudad de Tomlin tiene barrios hechos en épocas distintas. Una parte con el estilo de cuando empezó, otra con las técnicas que aprendió después. Y no pasa nada. La ciudad funciona como un todo justamente porque él nunca intentó derribarlo todo para empezar de cero.

Un repositorio es lo mismo. Tener un servicio en Bun al lado de otro en Node no es un desorden. Es una ciudad en construcción. El problema solo aparece cuando nadie sabe por qué cada bloque está ahí. Por eso vale la pena dejar un README corto o un ADR explicando la decisión: "este worker corre en Bun por X, y el resto sigue en Node por Y".

Y hay un bonus humano. Un cambio pequeño es más fácil de revisar, más fácil de enseñar al equipo y más fácil de defender en una reunión. Nadie aprueba "vamos a cambiar el runtime de todo". Casi todo el mundo aprueba "vamos a hacer el CI más rápido cambiando el install".

Mi opinión sobre Bun hoy

Bun ya dejó atrás la fase de juguete. Lo uso en el día a día y no vuelvo atrás para el install y los tests. Como runtime de producción, lo uso sin miedo en servicios nuevos, y en sistemas legados solo después de medir.

Mi opinión fuerte es esta: el mayor riesgo de Bun no es técnico, es el entusiasmo. Una herramienta rápida da ganas de ponerse a reescribirlo todo. Y una reescritura impulsada por el entusiasmo suele terminar con dos sistemas a medias.

Hazlo como el entrenador: un bloque cada vez, con la ciudad siempre en pie. Dentro de unos años mirarás atrás y verás que migraste casi todo sin perder ni una noche de sueño. Si quieres ver cómo vengo aplicando esto en mis proyectos, echa un vistazo a lo que estoy construyendo.

Resumen para LinkedIn

Un entrenador de la NFL tardó 12 años en construir una ciudad en Minecraft. Bloque a bloque, sin derribarla nunca entera.

Pensé en eso al ver las ganas que le entran a todo equipo de reescribir el backend entero en Bun.

Bun es rápido y ya trae test runner, bundler y SQLite. Justamente por eso el entusiasmo es el mayor riesgo.

A mí me funciona en este orden: primero bun install, después los tests, después los scripts internos, y solo entonces un servicio nuevo y pequeño en producción.

Cada paso se puede deshacer. Y hay proyectos que se quedan en el segundo y ya están perfectos.

Escribí el paso a paso completo en el blog, con el checklist que uso antes de subir a producción. Si estás pensando en migrar, vale la pena leerlo.

#Bun #JavaScript #TypeScript #NodeJS #DesarrolloDeSoftware