Bun.sql y los nuevos drivers nativos: bases de datos sin ORM pesado
Bun integra en el propio runtime clientes nativos para Postgres, MySQL y SQLite, sin instalar nada. Analizamos qué ofrece Bun.sql frente a un ORM tradicional y en qué casos conviene quedarse solo con SQL.
Durante años, hablar con una base de datos SQL desde Node.js significaba elegir entre dos caminos: instalar un driver de bajo nivel (pg, mysql2) y escribir consultas a mano, o instalar un ORM que te diera modelos, migraciones y tipado a cambio de una capa de abstracción adicional. Bun ha decidido que ese primer paso —el driver— no debería depender de ningún paquete de npm. Desde la versión 1.3, Bun.sql es un cliente nativo para PostgreSQL, MySQL y SQLite integrado directamente en el runtime, sin instalar nada y sin dependencias en el node_modules. No sustituye a un ORM, pero cambia la pregunta de partida: si el driver ya viene incluido y es rápido, ¿para qué proyectos sigue mereciendo la pena añadir una capa más encima?
Qué es exactamente Bun.sql
Bun.sql (accesible también como import { sql } from "bun") es un cliente SQL con API de plantillas etiquetadas (tagged templates), en la línea de lo que ya popularizó postgres.js en el ecosistema Node:
import { sql } from "bun";
const activeUsers = await sql`
SELECT id, email, created_at
FROM users
WHERE active = ${true}
ORDER BY created_at DESC
LIMIT ${20}
`;
Los valores interpolados en la plantilla no se concatenan como texto: Bun los envía como parámetros vinculados al motor de base de datos, exactamente igual que haría un prepared statement clásico. Esto es lo que hace que esta forma de escribir SQL sea segura por defecto frente a inyección, siempre que no rompas el patrón concatenando strings manualmente antes de pasarlos a la plantilla.
Lo que soporta hoy, de forma nativa y sin paquetes adicionales:
- PostgreSQL: autenticación SCRAM-SHA-256,
LISTEN/NOTIFY, pool de conexiones configurable, conexiones reservadas consql.reserve(), transacciones con savepoints y soporte para transacciones distribuidas (2PC). - MySQL (5.7+ y 8.0+): incorporado como driver nativo en Bun 1.3, con múltiples result sets, caché de prepared statements y negociación de plugins de autenticación.
- SQLite: ejecución síncrona, soporte de
PRAGMAy un sistema de tipos flexible, pensado para desarrollo local o aplicaciones embebidas.
Por qué esto es distinto de “otro driver más”
La diferencia no es solo técnica, es de distribución. pg o mysql2 son paquetes de npm que instalas, versionas y actualizas como cualquier otra dependencia; Bun.sql viene compilado dentro del binario del propio runtime, con las partes críticas de parseo de protocolo escritas en Zig y C++ en lugar de JavaScript. Esto se traduce en dos ventajas prácticas: cero instalación (nada que auditar en node_modules para hablar con la base de datos) y un rendimiento de bajo nivel más cercano al de un driver nativo compilado que al de una implementación en JavaScript puro sobre el runtime de V8.
La contrapartida es el acoplamiento: el código que usa Bun.sql directamente solo corre sobre Bun. Si tu aplicación necesita desplegarse también en Node.js, en un entorno serverless que no soporte Bun, o simplemente quieres mantener la opción abierta de cambiar de runtime en el futuro, esa portabilidad tiene un coste que hay que sopesar contra la ganancia de rendimiento.
Bun.sql frente a un ORM: qué capa resuelve cada cosa
Aquí es donde conviene ser preciso con el vocabulario, porque “ORM” y “driver” no compiten en la misma capa. Un ORM como Prisma o un query builder tipado como Drizzle se apoyan, por debajo, en un driver que habla el protocolo de red de la base de datos: ese driver puede ser pg, postgres.js… o, desde hace poco, el propio Bun.sql. De hecho, Drizzle ya soporta Bun.sql como uno de sus drivers de conexión, lo que significa que no es una elección binaria entre “Bun.sql” o “Drizzle”: puedes usar ambos juntos, con Drizzle aportando el esquema tipado y las migraciones, y Bun.sql como motor de conexión por debajo.
Dicho esto, la pregunta real que se plantea la mayoría de equipos es otra: ¿necesito la capa de abstracción de un ORM, o me basta con escribir SQL directamente contra Bun.sql? Ahí sí hay un trade-off real:
Solo Bun.sql | Drizzle | Prisma | |
|---|---|---|---|
| Filosofía | SQL directo, sin abstracción | Query builder tipado, cercano al SQL | Esquema declarativo, motor propio |
| Migraciones | Manuales (tus propios scripts SQL) | drizzle-kit, genera SQL a partir del esquema TS | Motor de migraciones propio, muy maduro |
| Tipado de resultados | Manual (tú defines los tipos de retorno) | Inferido automáticamente del esquema | Generado a partir del esquema Prisma |
| Tamaño en el bundle | Nulo (viene en el runtime) | Muy ligero (~7 KB con cero dependencias) | Mayor, aunque Prisma 7 lo redujo bastante con su nuevo compilador TypeScript/WASM |
| Curva de aprendizaje | Ninguna si ya sabes SQL | Baja si vienes de SQL | Media, hay que aprender su DSL y su cliente generado |
| Portabilidad de runtime | Solo Bun | Node, Bun, Deno, edge | Node, Bun (soporte edge más limitado) |
Prisma 7 (finales de 2025) sustituyó su motor de consultas basado en Rust por un compilador en TypeScript/WASM, con mejoras de hasta 3,4 veces en velocidad de consulta y una reducción de tamaño de paquete cercana al 90% respecto a versiones anteriores. Sigue siendo, aun así, una capa más pesada que Drizzle o que usar Bun.sql desnudo, precisamente porque resuelve más cosas por ti: generación de cliente, migraciones declarativas, un motor de validación de esquema propio.
Cuándo tiene sentido prescindir del ORM
Ir sin ORM (o con un query builder mínimo por encima de Bun.sql) es una decisión razonable cuando se cumplen varias de estas condiciones a la vez, no solo una:
- El equipo ya domina SQL y prefiere controlar exactamente qué consulta se ejecuta, sin depender de que el generador de consultas de un ORM produzca el plan óptimo (los
JOINgenerados automáticamente y el problema de N+1 queries son la fuente de dolor más habitual con ORMs mal usados). - El número de tablas y relaciones es manejable a mano. Un servicio pequeño con diez tablas no necesita un motor de migraciones declarativo; un monolito con doscientas sí se beneficia de tener el esquema como código versionado y generado.
- El rendimiento y el arranque en frío importan de verdad: funciones serverless o edge donde cada milisegundo de cold start cuenta se benefician de no cargar un cliente generado ni un motor de consultas adicional.
- Ya usas Bun como runtime de producción, no solo en local, y no hay planes de portar ese servicio a otro entorno a corto plazo.
Cuándo el ORM sigue ganando
Prescindir del ORM dejar de tener sentido, y conviene ser honesto al respecto, en estos escenarios:
- Equipos grandes con muchos desarrolladores tocando el mismo esquema. Las migraciones declarativas y el tipado generado automáticamente evitan errores de sincronización entre el código y el estado real de la base de datos que, a mano, dependen de la disciplina de cada persona.
- Aplicaciones con un dominio de datos complejo (relaciones profundas, herencia de tablas, validaciones de negocio ligadas al esquema) donde escribir y mantener SQL a mano para cada caso se vuelve más costoso que aprender la capa de abstracción.
- Necesitas portabilidad de runtime real. Si el mismo código de acceso a datos debe correr en Node.js, en Bun y potencialmente en un entorno edge distinto, atarte a
Bun.sqldirectamente introduce una dependencia de runtime que un ORM con múltiples drivers (o incluso Drizzle usando otro driver comopostgres.js) evita. - El equipo es junior en SQL o rota con frecuencia. Un ORM con buen tipado actúa como red de seguridad: es más difícil escribir una consulta que compile pero sea semánticamente incorrecta cuando el compilador conoce el esquema completo.
Migraciones y esquema: la pieza que casi nunca se automatiza sola
Si decides prescindir de un ORM completo, el punto que hay que resolver explícitamente son las migraciones. Bun.sql no incluye ningún sistema de migraciones: es un cliente de conexión y ejecución, no un gestor de esquema. Las alternativas habituales son mantener una carpeta de archivos .sql numerados y aplicarlos en orden con un script propio, o usar una herramienta de migraciones independiente del ORM (como node-pg-migrate para Postgres) que no impone ningún query builder por encima. Lo que no es sostenible a medio plazo es no tener ningún proceso de migración versionado y aplicar cambios de esquema manualmente en producción: eso es exactamente el tipo de problema que un ORM resuelve de fábrica y que, si prescindes de él, tienes que resolver tú con la misma disciplina.
Para quien ya trabaja con PostgreSQL como base de datos por defecto —sigue siendo la opción más razonable para la mayoría de proyectos nuevos en 2026, como se explica en PostgreSQL en 2026: por qué sigue siendo la base de datos por defecto—, Bun.sql es una forma legítima de reducir una capa de dependencias sin perder seguridad frente a inyección ni rendimiento. La recomendación práctica: si tu proyecto ya vive en Bun y el esquema es manejable, prueba primero con Bun.sql puro o con Drizzle usándolo como driver; añade Prisma cuando el coste real de mantener el esquema a mano supere el beneficio de no tener una capa de abstracción encima.
Artículos relacionados
Bun vs Node.js vs Deno en 2026: qué runtime elegir y por qué
Tres runtimes de JavaScript, tres filosofías distintas. Esto es lo que dicen los datos (varios, no uno solo) y cuándo cada uno gana de verdad.
Migraciones de bases de datos sin miedo: estrategias para producción
Por qué una migración de base de datos sale mal casi siempre por el mismo motivo, y cómo el patrón expand-contract lo evita sin necesitar una ventana de mantenimiento.
Índices en bases de datos relacionales: la guía que necesitabas
Cómo funcionan los índices por dentro, cuándo un índice ayuda y cuándo perjudica tus escrituras, índices compuestos y cómo leer un EXPLAIN ANALYZE sin adivinar.