Cambiar tema

Guía práctica de Drizzle ORM: un ORM TypeScript 90% más ligero que Prisma

Easton editorial illustration: component assembly loom

La semana pasada desplegué el proyecto en Vercel y la home tardó 3 segundos enteros en cargar. ¡3 segundos! Los usuarios ya se habían ido.

Abrí el análisis de bundle de Vercel y me quedé mirando el paquete Prisma Client de 14 MB, cuestionándome la vida. ¿Por qué cargar tanto código para consultas simples de usuarios? Peor aún: el cold start en Lambda era desesperante—sin Prisma, 600 ms; con Prisma, 2,5 s.

Dirás: «Prisma funciona muy bien». Yo también lo pensaba. Tipado seguro, migraciones automáticas, Prisma Studio… lo tiene todo. Pero el problema es que pesa demasiado.

Si te suena: cold starts lentos en serverless, bundles que se pasan de tamaño y consultas complejas que acaban en raw SQL, este artículo es para ti. Hoy presento Drizzle ORM: el núcleo pesa solo 7,4 kb, más del 90% menos que Prisma, con el mismo tipado seguro.

Te guiaré para configurar Next.js + Drizzle desde cero, explicaré la API SQL-like y compararé a fondo el rendimiento con Prisma. Sobre todo, cuándo elegir Drizzle y cuándo Prisma sigue siendo mejor opción.

¿Por qué Drizzle? Los dolores de los ORM actuales

Tres grandes dolores de Prisma

Prisma encaja bien en muchos escenarios. Pero con el uso, aparecen problemas difíciles de esquivar.

Dolor 1: el bundle se descontrola

El Client generado por Prisma v5 puede llegar a 14 MB. ¿Qué implica? Tu Next.js completo puede pesar 2-3 MB y Prisma se come casi la mitad. Prisma 7 lo bajó a ~1 MB (sin binario Rust), pero si sigues en versiones antiguas o el bundle importa mucho, es un problema serio.

Teníamos un chat en tiempo real en Cloudflare Workers con tope de 1 MB. Prisma lo superaba y tuvimos que cambiar.

Dolor 2: cold starts serverless lentos

En GitHub Issue #10724 llevan años debatiendo los cold starts lentos de Prisma en Lambda. Los datos son claros:

  • Funciones sin ORM: ~600 ms
  • Funciones con Prisma v5: ~2,5 s
  • Funciones con Prisma v7: ~1,5 s (mejor, pero aún lento)

Prisma debe parsear al arrancar un DMMF (Data Model Meta Format) enorme. En schemas medianos puede superar 6 millones de caracteres. Cada cold start lo vuelve a parsear: difícil que vaya rápido.

Cal.com (calendario open source) publicó un post técnico sobre optimizar cold starts. Conclusión: el problema existe y solo se mitiga con workarounds.

Dolor 3: poco control sobre SQL

Prisma quiere «abstraer SQL» y que escribas con su DSL. Suele ir bien, pero en consultas complejas se complica.

Si necesitas JOINs múltiples, subconsultas y agregaciones condicionales, a veces la API de Prisma no basta y acabas en prisma.$queryRaw escribiendo SQL a mano.

Si al final escribes SQL, ¿por qué no empezar con una API SQL-like? Esa es la apuesta de Drizzle.

Lo que los desarrolladores necesitan de verdad

Resumiendo lo que buscan los desarrolladores:

  1. Tipado seguro sin sacrificar rendimiento: inferencia TypeScript sí, pero el ORM no debe frenar el proyecto
  2. SQL directo, sin DSL nuevo: SQL basta; no quiero otra sintaxis de consultas
  3. Compatible con serverless: en 2025 casi todos usamos Vercel, Cloudflare Workers o AWS Lambda
  4. Compilación rápida: en proyectos grandes, la inferencia de Prisma ralentiza TypeScript—un coste oculto

Drizzle nació para cubrir esto.

¿Qué es Drizzle ORM? Características clave

La filosofía de diseño de Drizzle

El lema de Drizzle es “If you know SQL, you know Drizzle”. Suena arrogante, pero cumple.

"If you know SQL, you know Drizzle."

Los ORM clásicos abstraen SQL. Drizzle hace lo contrario: no abstrae SQL; acerca la API TypeScript a la sintaxis SQL. El código se parece a SQL, con tipado completo.

Un ejemplo:

// Consulta Drizzle
await db
  .select()
  .from(posts)
  .leftJoin(comments, eq(posts.id, comments.postId))
  .where(eq(posts.id, 10))

// Generated SQL
SELECT * FROM posts
LEFT JOIN comments ON posts.id = comments.post_id
WHERE posts.id = 10

¿Ves? La estructura casi replica SQL. Quien sabe SQL lo entiende al instante.

Características principales

1. Ligereza extrema

El paquete drizzle-orm pesa solo 7,4 kb (min+gzip) y no tiene dependencias en runtime. Compara:

  • Drizzle: ~7.4kb
  • TypeORM: ~300kb
  • Prisma v7: ~1MB
  • Prisma v5: ~14MB

No están ni en el mismo orden de magnitud.

2. TypeScript primero, sin generar Client

Prisma exige prisma generate para el Client. Drizzle no.

Defines el schema y TypeScript infiere los tipos. IntelliSense, comprobación de tipos, avisos de error: todo en compilación, no en runtime.

3. API SQL-like con curva de aprendizaje casi nula

Si sabes SQL, arrancas con Drizzle en menos de 10 minutos.

// SELECT query
db.select().from(users).where(eq(users.id, 1))

// INSERT
db.insert(users).values({ name: 'John', email: '[email protected]' })

// UPDATE
db.update(users).set({ name: 'Jane' }).where(eq(users.id, 1))

// DELETE
db.delete(users).where(eq(users.id, 1))

Quien conoce SQL no necesita mirar la documentación.

4. Cero sobrecoste de rendimiento

Drizzle no añade capa de abstracción en runtime: las consultas van directo a SQL.

A diferencia de Prisma, que parsea DMMF al arrancar y mantiene estado interno, Drizzle es un constructor de consultas puro. Sin costes ocultos ni sorpresas.

5. Listo para serverless

Drizzle soporta los principales entornos serverless:

  • Vercel Edge Functions
  • Cloudflare Workers
  • AWS Lambda
  • Deno Deploy
  • Bun

Y drivers serverless nativos:

  • Neon Serverless
  • PlanetScale
  • Turso (SQLite on the edge)
  • Supabase

Usamos Neon + Drizzle en Vercel Edge: el cold start bajó de 2,5 s a 700 ms. Efecto real.

Casos de uso

Drizzle no lo resuelve todo, pero encaja muy bien en:

1. Aplicaciones serverless

Si tu app corre en Lambda o Edge Functions, la ligereza y el arranque rápido de Drizzle son clave.

2. Escenarios sensibles al rendimiento

Apps en tiempo real, finanzas, analítica: cualquier escenario sensible a la latencia gana con el diseño sin abstracción de Drizzle.

3. Proyectos que exigen control SQL avanzado

Con muchas consultas complejas y optimización SQL manual, la API SQL-like de Drizzle supera al DSL de Prisma.

4. Proyectos full-stack con bundle limitado

Frameworks como SolidStart o Qwik empaquetan ORM en el cliente; ahí 7,4 kb marcan diferencia.

Al revés: si el equipo domina poco SQL, necesita prototipar rápido o valora Prisma Studio, Migrate o Pulse, Prisma puede seguir siendo mejor.

Configuración práctica: Next.js + Drizzle

Menos teoría, más práctica: configuraremos Next.js 15 + Drizzle + PostgreSQL desde cero.

Preparar el entorno

Crea el proyecto Next.js:

npx create-next-app@latest my-drizzle-app
cd my-drizzle-app

Instala las dependencias de Drizzle:

npm install drizzle-orm drizzle-kit
npm install @neondatabase/serverless  # Si usas Neon
# o
npm install postgres  # Si usas PostgreSQL tradicional

Recomiendo Neon: PostgreSQL serverless que encaja muy bien con Drizzle. Cuenta gratis, crea la base de datos y copia la cadena de conexión.

Definir el schema de la base de datos

Crea db/schema.ts y define las tablas:

import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

// Users table
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow(),
});

// Posts table
export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content'),
  authorId: integer('author_id').references(() => users.id),
  createdAt: timestamp('created_at').defaultNow(),
});

// Define relations (one-to-many)
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));

Es TypeScript puro: sin schema especial de Prisma ni generar Client.

Configurar la conexión a la base de datos

Crea db/index.ts:

import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';

// Leer conexión desde variable de entorno
const sql = neon(process.env.DATABASE_URL!);

// Instancia Drizzle
export const db = drizzle(sql, { schema });

Así de simple. En .env.local:

DATABASE_URL=postgres://user:[email protected]/dbname

Configurar Drizzle Kit (herramienta de migraciones)

Crea drizzle.config.ts:

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Generar archivos de migración:

npx drizzle-kit generate

Genera migraciones SQL en drizzle/. Revísalas y, si cuadra, ejecuta:

npx drizzle-kit push

Las tablas quedan creadas.

Estructura final del proyecto

my-drizzle-app/
├── app/                  # Next.js application
│   ├── page.tsx
│   └── actions.ts        # Server Actions
├── db/
│   ├── schema.ts         # Table definitions and relations
│   └── index.ts          # Database connection
├── drizzle/
│   └── migrations/       # Migration files (auto-generated)
├── drizzle.config.ts     # Drizzle Kit configuration
├── .env.local            # Environment variables
└── package.json

Todo el setup tarda menos de 5 minutos: sin schema Prisma complejo ni prisma generate largo, solo TypeScript.

API SQL-like de Drizzle en la práctica

Veamos consultas con Drizzle: operaciones habituales y uso en Next.js Server Actions.

Operaciones CRUD básicas

Consultar datos (SELECT)

import { db } from '@/db';
import { users, posts } from '@/db/schema';
import { eq, like, and, or, desc } from 'drizzle-orm';

// Consultar todos los usuarios
const allUsers = await db.select().from(users);

// Consultar un usuario
const user = await db.select().from(users).where(eq(users.id, 1));

// Búsqueda parcial
const result = await db.select().from(users).where(like(users.name, '%John%'));

// Condiciones compuestas
const admins = await db
  .select()
  .from(users)
  .where(
    and(
      eq(users.role, 'admin'),
      gt(users.createdAt, new Date('2024-01-01'))
    )
  );

// Ordenar y limitar
const latestPosts = await db
  .select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(10);

Es lógica SQL con llamadas a funciones.

Insertar datos (INSERT)

// Insertar un registro
await db.insert(users).values({
  name: 'John Doe',
  email: '[email protected]',
});

// Insertar varios registros
await db.insert(users).values([
  { name: 'Alice', email: '[email protected]' },
  { name: 'Bob', email: '[email protected]' },
]);

// Devolver lo insertado
const [newUser] = await db
  .insert(users)
  .values({ name: 'Charlie', email: '[email protected]' })
  .returning();

console.log(newUser.id); // ID autogenerado

Actualizar datos (UPDATE)

// Actualizar un registro
await db
  .update(users)
  .set({ name: 'Jane Doe' })
  .where(eq(users.id, 1));

// Actualización masiva
await db
  .update(posts)
  .set({ published: true })
  .where(eq(posts.authorId, 1));

// Devolver lo actualizado
const [updatedUser] = await db
  .update(users)
  .set({ name: 'Updated Name' })
  .where(eq(users.id, 1))
  .returning();

Eliminar datos (DELETE)

// Eliminar un registro
await db.delete(users).where(eq(users.id, 1));

// Eliminación masiva
await db.delete(posts).where(eq(posts.published, false));

// Devolver lo eliminado
const deleted = await db
  .delete(users)
  .where(eq(users.id, 1))
  .returning();

Consultas avanzadas

Operaciones JOIN

// LEFT JOIN: usuarios y sus posts
const usersWithPosts = await db
  .select({
    userId: users.id,
    userName: users.name,
    postId: posts.id,
    postTitle: posts.title,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));

// INNER JOIN: solo usuarios con posts
const activeAuthors = await db
  .select()
  .from(users)
  .innerJoin(posts, eq(users.id, posts.authorId));

Subconsultas

// Usuarios con más de 5 posts
const sq = db
  .select({ authorId: posts.authorId, count: count() })
  .from(posts)
  .groupBy(posts.authorId)
  .having(gt(count(), 5))
  .as('sq');

const prolificAuthors = await db
  .select()
  .from(users)
  .innerJoin(sq, eq(users.id, sq.authorId));

Funciones de agregación

import { count, sum, avg } from 'drizzle-orm';

// Contar usuarios totales
const [{ total }] = await db
  .select({ total: count() })
  .from(users);

// Contar posts por autor
const postCounts = await db
  .select({
    authorId: posts.authorId,
    count: count(),
  })
  .from(posts)
  .groupBy(posts.authorId);

Uso en Next.js Server Actions

Crea app/actions.ts:

'use server';

import { db } from '@/db';
import { users } from '@/db/schema';
import { eq } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';

// Crear usuario
export async function createUser(formData: FormData) {
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;

  try {
    await db.insert(users).values({ name, email });
    revalidatePath('/users');
    return { success: true };
  } catch (error) {
    return { success: false, error: 'Error al crear usuario' };
  }
}

// Obtener todos los usuarios
export async function getUsers() {
  return await db.select().from(users);
}

// Eliminar usuario
export async function deleteUser(id: number) {
  try {
    await db.delete(users).where(eq(users.id, id));
    revalidatePath('/users');
    return { success: true };
  } catch (error) {
    return { success: false, error: 'Error al eliminar usuario' };
  }
}

Uso en páginas:

// app/users/page.tsx
import { getUsers } from '../actions';

export default async function UsersPage() {
  const users = await getUsers();

  return (
    <div>
      <h1>Lista de usuarios</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

Seguridad de tipos en TypeScript

Aquí brilla Drizzle: inferencia de tipos completa.

// Tipo de retorno inferido automáticamente
const users = await db.select().from(users);
// Type: { id: number; name: string; email: string; createdAt: Date }[]

// Campos personalizados con tipos inferidos
const result = await db
  .select({
    id: users.id,
    name: users.name,
  })
  .from(users);
// Type: { id: number; name: string }[]

// Errores detectados en compilación
await db.select().from(users).where(eq(users.id, '1'));
// ❌ Error TypeScript: el tipo 'string' no es asignable al tipo 'number'

IntelliSense sugiere campos, funciones y operadores. Escribir código es casi un juego: un punto y aparece todo.

No hace falta memorizar la API. El compilador TypeScript es la mejor documentación.

Drizzle vs Prisma: comparativa en profundidad

He alabado Drizzle, pero comparemos con objetividad para decidir qué encaja en tu proyecto.

Comparativa de rendimiento

~7.4kb
Drizzle Bundle
vs Prisma 14MB
~600 ms
Cold start
vs Prisma 2,5 s
0
Deps runtime
TypeScript puro
Source: Datos de monitorización en producción
DimensiónDrizzlePrisma v5Prisma v7Notas
Tamaño del bundle~7,4 kb~14 MB~1 MBDrizzle es el más ligero
Cold start~600 ms~2,5 s~1,5 sEntorno serverless
Dependencias runtime0Binario Rust0Drizzle y Prisma v7 sin deps
Memoria~5 MB~80 MB~30 MB (est.)Memoria en runtime
Velocidad de tiposRápidaMediaMediaInferencia más simple en Drizzle

Datos reales

Migré un proyecto de Prisma v5 a Drizzle:

  • Primera petición: 3 s → 700 ms (76% más rápido)
  • Bundle en producción: 18 MB → 4 MB (78% menos)
  • Cold start Lambda: 2,4 s → 650 ms (73% más rápido)

Estas cifras no salen de un entorno de pruebas: son datos reales de monitorización en producción.

Comparativa de experiencia de desarrollo

Forma de definir el schema

// Drizzle (TypeScript code)
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
});

// Prisma (Dedicated DSL)
model User {
  id   Int    @id @default(autoincrement())
  name String
}

Ventaja Drizzle: TypeScript puro con buen soporte IDE; condicionales, genéricos, etc.

Ventaja Prisma: el schema Prisma es más conciso y legible

Estilo de la API de consultas

// Drizzle (SQL-like)
await db
  .select()
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId))
  .where(gt(posts.views, 1000));

// Prisma (Chained API)
await prisma.user.findMany({
  include: {
    posts: {
      where: { views: { gt: 1000 } },
    },
  },
});

Ventaja Drizzle: más cerca de SQL; consultas complejas más fáciles

Ventaja Prisma: más amigable sin SQL; consultas anidadas más claras

Comparativa de funcionalidades

Ventajas de Drizzle

  1. Muy ligero: núcleo de 7,4 kb, ideal si el bundle importa
  2. Nativo serverless: cold starts rápidos, perfecto para Edge
  3. Control SQL fuerte: más flexibilidad en consultas y optimización
  4. Sin generar Client: cambias schema y usas al momento, sin prisma generate
  5. Tree-shakable: solo empaquetas lo que usas

Ventajas de Prisma

  1. Ecosistema maduro: desde 2021, comunidad grande, muchos tutoriales
  2. Toolchain completa: Prisma Studio, Prisma Migrate, Prisma Pulse
  3. Consultas relacionales inteligentes: mitiga N+1, anidadas más intuitivas
  4. Amigable para principiantes: menos SQL profundo, DSL más suave
  5. Errores más claros: mensajes de runtime más útiles

Criterios de elección

Elige Drizzle cuando:

✅ Entornos Serverless/Edge (Vercel, Cloudflare Workers, Deno Deploy)
✅ Escenarios sensibles al rendimiento (apps en tiempo real, sistemas financieros, APIs de alta concurrencia)
✅ El bundle está limitado (entornos con tope < 1 MB)
✅ El equipo domina SQL y prefiere control directo de las consultas
✅ El proyecto requiere optimización SQL compleja

Elige Prisma cuando:

✅ El equipo domina poco SQL y necesita arrancar rápido
✅ Se valora la experiencia de desarrollo y una toolchain completa
✅ Se necesita gestión visual de la base de datos (Prisma Studio)
✅ Hay consultas relacionales complejas y modelado de datos avanzado
✅ El proyecto no es serverless y el tamaño del bundle no importa

Mi recomendación personal

En proyectos nuevos, yo haría así:

  • Proyectos personales/startups: Drizzle (rendimiento, coste bajo, optimización máxima)
  • Empresa/equipo: según SQL del equipo—fuerte → Drizzle; débil → Prisma
  • Serverless-first: Drizzle sin dudarlo
  • Servidor tradicional: ambos valen; Prisma tiene más herramientas

También puedes mezclar: Drizzle en módulos críticos, Prisma en admin. Conviven sin conflicto.

Guía de migración y buenas prácticas

Migrar de Prisma a Drizzle

Si ya tienes Prisma y quieres probar Drizzle, migra de forma progresiva.

Paso 1: conversión de schema

// Prisma Schema
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  posts     Post[]
  createdAt DateTime @default(now())
}

// Convertir a schema Drizzle
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow(),
});

Paso 2: reescritura de consultas

// Consulta Prisma
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true },
});

// Consulta Drizzle
const [user] = await db
  .select()
  .from(users)
  .where(eq(users.id, 1))
  .leftJoin(posts, eq(users.id, posts.authorId));

Paso 3: reemplazo progresivo

Puedes mantener Prisma y Drizzle a la vez:

// Consultas sensibles al rendimiento con Drizzle
import { db } from '@/db/drizzle';
const latestPosts = await db
  .select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(50);

// Consultas relacionales complejas: Prisma de forma temporal
import { prisma } from '@/db/prisma';
const userWithRelations = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: { include: { comments: { include: { author: true } } } },
  },
});

Sustituye poco a poco para reducir riesgo.

Buenas prácticas con Drizzle

1. Gestión del pool de conexiones

En serverless, cuida el pool de conexiones:

import { drizzle } from 'drizzle-orm/neon-http';
import { neon, neonConfig } from '@neondatabase/serverless';

// Configurar pool de conexiones
neonConfig.fetchConnectionCache = true;

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });

2. Consultas preparadas

Consultas frecuentes: prepáralas para mejor rendimiento:

import { db } from '@/db';
import { users } from '@/db/schema';
import { eq } from 'drizzle-orm';

// Preparar consulta
const getUserById = db
  .select()
  .from(users)
  .where(eq(users.id, placeholder('id')))
  .prepare('get_user_by_id');

// Usar consulta preparada
const user = await getUserById.execute({ id: 1 });

3. Manejo de transacciones

await db.transaction(async (tx) => {
  // Crear usuario
  const [user] = await tx
    .insert(users)
    .values({ name: 'John', email: '[email protected]' })
    .returning();

  // Crear posts asociados
  await tx.insert(posts).values({
    title: 'First Post',
    authorId: user.id,
  });

  // Si falla un paso, rollback total
});

4. Reutilizar tipos

Exporta tipos inferidos para el frontend:

// db/schema.ts
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull(),
});

// Exportar tipos inferidos
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;

// Uso en frontend
import type { User } from '@/db/schema';

function UserCard({ user }: { user: User }) {
  return <div>{user.name}</div>;
}

5. Manejo de errores

import { db } from '@/db';
import { users } from '@/db/schema';

try {
  await db.insert(users).values({
    name: 'John',
    email: '[email protected]',
  });
} catch (error) {
  // Códigos de error PostgreSQL
  if (error.code === '23505') {
    console.error('El correo ya existe');
  } else {
    console.error('Error de base de datos', error);
  }
}

Conclusión

Para cerrar.

Drizzle ORM aporta aire fresco: en lugar de esconder SQL, lo abraza. Con 7,4 kb, cero dependencias en runtime y rendimiento casi nativo, demuestra que ligero no es compromiso, es mejor opción.

No sustituye del todo a Prisma, que tiene ecosistema y herramientas más maduros. Pero si tu proyecto:

  • Corre en serverless
  • Es sensible al rendimiento y al bundle
  • Necesita control SQL avanzado
  • Tiene equipo con base SQL

Drizzle puede encajar mejor.

Nuestros datos lo confirman: tras migrar, cold start 73% más rápido, bundle 78% menor, primera petición de 3 s a 700 ms. No es micro-optimización: es un salto cualitativo.

Si Prisma te frena por cold starts y bundles grandes, prueba Drizzle. Si sabes SQL, en 10 minutos arrancas.

Enlaces útiles:

Pruébalo. Como a mí, puede que no quieras volver.

Guía completa de configuración y migración Next.js + Drizzle ORM

De cero a Drizzle ORM: entorno, schema, conexión, consultas y migración desde Prisma

Estimated time: PT2H

  1. 1

    Step 1: Paso 1: entorno e instalación de dependencias

    Crear proyecto Next.js:
  2. 2

    Step 2: Paso 2: definir schema

    Crear db/schema.ts con las tablas:
  3. 3

    Step 3: Paso 3: conexión y Drizzle Kit

    Crear db/index.ts:
  4. 4

    Step 4: Paso 4: API SQL-like

    Operaciones CRUD básicas:
  5. 5

    Step 5: Paso 5: Server Actions en Next.js

    Crear app/actions.ts:
  6. 6

    Step 6: {user.name}

    {user.email}
  7. 7

    Step 7: Paso 6: migrar desde Prisma (opcional)

    Migración progresiva si ya usas Prisma:

FAQ

¿Qué diferencia hay entre Drizzle ORM y Prisma? ¿Cuándo elegir Drizzle?
Diferencias clave:

Rendimiento:
• Drizzle: 7.4kb, 600ms cold start, cero dependencias en runtime
• Prisma v5: 14MB, 2.5s cold start, requiere binario Rust
• Prisma v7: 1MB, 1.5s cold start, mejorado pero aún más lento que Drizzle

Experiencia de desarrollo:
• Drizzle: API SQL-like: si sabes SQL, sabes Drizzle; no hace falta generar Client
• Prisma: DSL propio, requiere aprendizaje y ejecutar prisma generate

Elige Drizzle cuando:
✅ Entornos Serverless/Edge (Vercel, Cloudflare Workers, Deno Deploy)
✅ Escenarios sensibles al rendimiento (apps en tiempo real, sistemas financieros, APIs de alta concurrencia)
✅ El bundle está limitado (entornos con tope < 1 MB)
✅ El equipo domina SQL y prefiere control directo de las consultas
✅ El proyecto requiere optimización SQL compleja

Elige Prisma cuando:
✅ El equipo domina poco SQL y necesita arrancar rápido
✅ Se valora la experiencia de desarrollo y una toolchain completa
✅ Se necesita gestión visual de la base de datos (Prisma Studio)
✅ Hay consultas relacionales complejas y modelado de datos avanzado
✅ El proyecto no es serverless y el tamaño del bundle no importa
¿Qué significa exactamente la API SQL-like de Drizzle ORM?
La API de Drizzle acerca el código TypeScript lo máximo posible a la sintaxis SQL:

Comparación de ejemplo:

SQL:
```sql
SELECT * FROM posts
LEFT JOIN comments ON posts.id = comments.post_id
WHERE posts.id = 10
```

Drizzle:
```typescript
await db
.select()
.from(posts)
.leftJoin(comments, eq(posts.id, comments.postId))
.where(eq(posts.id, 10))
```

Características clave:
• La estructura del código casi replica SQL
• Quien sabe SQL lo entiende de un vistazo
• Curva de aprendizaje casi nula (si sabes SQL)
• Inferencia de tipos completa e IntelliSense
• Errores detectados en compilación

Comparado con el DSL de Prisma:
```typescript
// Prisma exige aprender una sintaxis de consulta nueva
await prisma.user.findMany({
include: {
posts: {
where: { views: { gt: 1000 } },
},
},
});
```

Drizzle está más cerca del SQL nativo; las consultas complejas son más fáciles de expresar.
¿Qué tan grandes son las ventajas de rendimiento de Drizzle ORM en entornos serverless?
Datos reales (migración de Prisma v5 a Drizzle):

Mejoras de rendimiento:
• Primera petición: 3 s → 700 ms (76% más rápido)
• Bundle en producción: 18 MB → 4 MB (78% menos)
• Cold start Lambda: 2,4 s → 650 ms (73% más rápido)

Análisis de causas:

1. Tamaño del bundle:
• El núcleo de Drizzle pesa solo 7,4 kb (min+gzip)
• El Client generado por Prisma v5 puede llegar a 14 MB
• En entornos con límite de 1 MB como Cloudflare Workers, Prisma supera el tope

2. Tiempo de cold start:
• Prisma debe parsear al arrancar un DMMF (Data Model Meta Format) enorme
• En schemas medianos puede superar 6 millones de caracteres
• Cada cold start debe parsearlo de nuevo, lo que ralentiza el arranque
• Drizzle no tiene capa de abstracción en runtime; las consultas se traducen directamente a SQL, sin costes ocultos

3. Uso de memoria:
• Drizzle: ~5MB
• Prisma v5: ~80MB
• Prisma v7: ~30 MB (estimado)

Estas cifras no salen de un entorno de pruebas: son datos reales de monitorización en producción.
¿Cómo migrar de Prisma a Drizzle? ¿Es complejo el proceso?
Puedes migrar de forma progresiva, sin reemplazar todo de golpe:

Paso 1: conversión de schema

Prisma Schema → Drizzle Schema:
• Los modelos Prisma pasan a definiciones pgTable
• Correspondencia de tipos: Int → serial/integer, String → text, DateTime → timestamp
• Las relaciones pasan de la sintaxis de asociación de Prisma a la función relations de Drizzle

Paso 2: reescritura de consultas

Consultas Prisma → consultas Drizzle:
• findUnique/findMany → select().from().where()
• include → leftJoin/innerJoin
• create → insert().values()
• update → update().set().where()

Paso 3: reemplazo progresivo

Puedes mantener Prisma y Drizzle a la vez:
```typescript
// Consultas sensibles al rendimiento con Drizzle
import { db } from '@/db/drizzle';
const latestPosts = await db
.select()
.from(posts)
.orderBy(desc(posts.createdAt))
.limit(50);

// Consultas relacionales complejas: Prisma de forma temporal
import { prisma } from '@/db/prisma';
const userWithRelations = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: { include: { comments: { include: { author: true } } } },
},
});
```

Sustituye poco a poco para reducir riesgo. La migración no es compleja: sobre todo conversión de sintaxis.
¿Qué bases de datos soporta Drizzle ORM? ¿En qué entornos puede ejecutarse?
Bases de datos soportadas:

• PostgreSQL (recomendado Neon Serverless)
• MySQL
• SQLite (incluye Turso, SQLite en el edge)
• SQL Server

Entornos serverless soportados:

• Vercel Edge Functions
• Cloudflare Workers
• AWS Lambda
• Deno Deploy
• Bun

Soporte nativo para drivers serverless:

• Neon Serverless (PostgreSQL)
• PlanetScale (MySQL)
• Turso (SQLite on the edge)
• Supabase (PostgreSQL)

Nuestro proyecto usa Neon + Drizzle desplegado en Vercel Edge; el cold start bajó de 2,5 s a 700 ms.

Ventajas clave:
• Soporta los principales entornos serverless
• Drivers serverless nativos
• Cero dependencias en runtime; funciona en cualquier entorno JavaScript
¿Cómo es la seguridad de tipos de Drizzle ORM? ¿Cómo se compara con Prisma?
Drizzle ofrece seguridad de tipos completa y es más simple que Prisma:

Inferencia de tipos:

Drizzle (inferencia automática):
```typescript
// Tipo de retorno inferido automáticamente
const users = await db.select().from(users);
// Type: { id: number; name: string; email: string; createdAt: Date }[]

// Campos personalizados con tipos inferidos
const result = await db
.select({
id: users.id,
name: users.name,
})
.from(users);
// Type: { id: number; name: string }[]

// Errores detectados en compilación
await db.select().from(users).where(eq(users.id, '1'));
// ❌ Error TypeScript: el tipo 'string' no es asignable al tipo 'number'
```

Características clave:
• Sin generar Client; TypeScript infiere los tipos
• IntelliSense sugiere campos, funciones y operadores
• Errores en compilación, no en runtime
• Comprobación de tipos más rápida (inferencia más simple)

Comparado con Prisma:
• Prisma exige prisma generate para crear el Client
• En proyectos grandes, la inferencia de Prisma ralentiza la compilación TypeScript
• Drizzle infiere de forma más simple y compila más rápido

Exportar tipos para el frontend:
```typescript
// Exportar tipos inferidos
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
```
¿Es Drizzle ORM adecuado para principiantes? ¿Cómo es la curva de aprendizaje?
Si sabes SQL, la curva de aprendizaje de Drizzle es muy suave:

Tiempo de arranque:
• Con SQL: arranque en 10 minutos
• Sin SQL: conviene aprender SQL antes

Por qué cuesta poco aprender:
• API SQL-like: estructura casi idéntica a SQL
• Sin aprender un DSL nuevo (como Prisma)
• Tipado completo e IntelliSense
• El compilador TypeScript es la mejor documentación

Ejemplo:
```typescript
// SELECT query
db.select().from(users).where(eq(users.id, 1))

// INSERT
db.insert(users).values({ name: 'John', email: '[email protected]' })

// UPDATE
db.update(users).set({ name: 'Jane' }).where(eq(users.id, 1))

// DELETE
db.delete(users).where(eq(users.id, 1))
```

Quien conoce SQL no necesita consultar documentación.

Comparado con Prisma:
• Prisma exige aprender su DSL
• Más amigable si no dominas SQL
• Si sabes SQL, Drizzle es más intuitivo

Recomendación:
• Equipo con buena base SQL: elige Drizzle
• Equipo sin SQL: Prisma, o aprende SQL y luego Drizzle

16 min de lectura · Publicado el: 20 dic 2025 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog