Alternar tema

Guia prático do Drizzle ORM: a alternativa TypeScript 90% mais leve que o Prisma

Easton editorial illustration: component assembly loom

Na semana passada, fiz o deploy de um projeto no Vercel e esperei três segundos inteiros até a página inicial abrir. Três segundos! A essa altura, o usuário já foi embora.

Abri a análise do bundle no Vercel e fiquei olhando para o Prisma Client ocupando 14 MB, tentando entender como chegamos ali. Era apenas uma consulta simples de usuário; por que carregar tanto código? Para piorar, o cold start do Lambda era desesperador: uma função sem Prisma iniciava em 600 ms, enquanto a versão com Prisma levava 2,5 segundos.

Você pode pensar: mas o Prisma não é fácil de usar? É, e eu também pensava assim. Ele oferece segurança de tipos, migrações automáticas e a interface visual do Prisma Studio. O problema é que ele é pesado demais.

Se você também enfrenta cold starts lentos em serverless, bundles acima do limite ou consultas complexas que acabam em raw SQL, este artigo é para você. O Drizzle ORM tem um pacote principal de apenas 7,4 kB, mais de 90% menor que o Prisma, sem abrir mão da segurança de tipos.

Neste guia, vamos configurar Next.js com Drizzle do zero, entender sua API semelhante a SQL e comparar em detalhes seu desempenho com o Prisma. E, principalmente, veremos quando escolher Drizzle e quando o Prisma ainda é a melhor opção.

Por que precisamos do Drizzle? Os problemas dos ORMs atuais

Três pontos fracos do Prisma

O Prisma é uma boa escolha em muitos cenários. Mas, com o tempo, alguns problemas ficam difíceis de contornar.

Problema 1: o bundle cresce sem controle

O Client gerado pelo Prisma v5 pode chegar a 14 MB. Para ter uma ideia, um projeto Next.js inteiro pode ocupar apenas 2 ou 3 MB; o Prisma sozinho passa a dominar o pacote. O Prisma 7 reduziu o tamanho para 1 MB ao remover o binário Rust, mas versões antigas e projetos sensíveis ao tamanho do bundle ainda sofrem bastante.

Nossa equipe tinha um aplicativo de chat em tempo real que precisava ser implantado no Cloudflare Workers, onde o bundle não podia passar de 1 MB. O Prisma excedeu o limite e tivemos de trocar de solução.

Problema 2: cold start lento em serverless

A issue #10724 no GitHub discute há anos o cold start lento do Prisma no Lambda. Os números são diretos:

  • Função sem ORM: cerca de 600 ms
  • Função com Prisma v5: cerca de 2,5 s
  • Função com Prisma v7: cerca de 1,5 s, uma melhora que ainda deixa a inicialização lenta

Isso acontece porque o Prisma precisa analisar, durante a inicialização, uma enorme string DMMF, ou Data Model Meta Format. Em um schema de porte médio, ela pode ter mais de seis milhões de caracteres. Fazer essa análise a cada cold start inevitavelmente custa tempo.

O Cal.com, uma ferramenta open source de agendamento, publicou um artigo técnico específico sobre as tentativas de otimizar esse tempo. A conclusão foi que o problema existe e só pode ser atenuado com vários workarounds.

Problema 3: pouco controle sobre SQL

A filosofia do Prisma é abstrair o SQL para que as consultas sejam escritas na DSL do Prisma Client. Isso é conveniente na maior parte do tempo, mas complica consultas avançadas.

Imagine um JOIN em várias camadas com subconsulta e agregação condicional. A API do Prisma pode não conseguir expressá-lo, obrigando você a recorrer a prisma.$queryRaw e escrever SQL diretamente.

Se o resultado final ainda é SQL, por que não adotar desde o início uma API semelhante a SQL? Essa é a proposta do Drizzle.

O que os desenvolvedores realmente precisam

Em resumo, estas são as necessidades mais comuns:

  1. Segurança de tipos sem sacrificar desempenho: queremos a inferência do TypeScript, mas não um ORM que deixe o projeto lento.
  2. Usar SQL sem aprender outra DSL: SQL já resolve o problema; não deveria ser necessário memorizar outra sintaxe de consulta.
  3. Boa integração com serverless: em 2025, Vercel, Cloudflare Workers e AWS Lambda já fazem parte do cotidiano.
  4. Compilação rápida: em projetos grandes, a inferência do Prisma também pode desacelerar a compilação TypeScript.

O Drizzle foi criado para atender justamente a essas necessidades.

O que é o Drizzle ORM? Principais recursos

A filosofia do Drizzle

O slogan do Drizzle é “If you know SQL, you know Drizzle”. Parece ousado, mas descreve bem a ferramenta.

"If you know SQL, you know Drizzle."

Enquanto ORMs tradicionais tentam esconder o SQL, o Drizzle faz o contrário: ele não abstrai SQL, mas aproxima sua API TypeScript o máximo possível da sintaxe SQL. O código se parece com SQL e ainda oferece dicas completas de tipos.

Veja este exemplo:

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

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

A estrutura do código é praticamente igual à do SQL. Quem já conhece a linguagem entende a consulta imediatamente.

Principais recursos

1. Extremamente leve

O pacote principal drizzle-orm tem apenas 7,4 kB com minificação e gzip, além de nenhuma dependência em runtime. Compare:

  • Drizzle: cerca de 7,4 kB
  • TypeORM: cerca de 300 kB
  • Prisma v7: cerca de 1 MB
  • Prisma v5: cerca de 14 MB

Não são diferenças pequenas; estamos falando de outra ordem de grandeza.

2. TypeScript-first, sem gerar Client

O Prisma exige a execução de prisma generate para criar o Client. O Drizzle não.

Depois que você define o schema, o TypeScript infere os tipos diretamente. IntelliSense, preenchimento automático, verificação de tipos e mensagens de erro funcionam sem uma etapa de geração. Os problemas aparecem na compilação, antes de chegar ao runtime.

3. API semelhante a SQL e curva de aprendizado mínima

Se você já escreve SQL, consegue começar com o Drizzle em menos de dez minutos.

// SELECT
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))

Quem conhece SQL entende essas operações sem precisar abrir a documentação.

4. Sem perda de desempenho

O Drizzle não adiciona uma camada de abstração em runtime. A consulta escrita por você é traduzida diretamente para SQL, sem etapas intermediárias.

Enquanto o Prisma analisa o DMMF na inicialização e mantém estado interno, o Drizzle funciona como um query builder direto: sem custo oculto ou sobrecarga inesperada.

5. Pronto para serverless

O Drizzle funciona nos principais ambientes serverless:

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

Também oferece suporte nativo a drivers de banco serverless:

  • Neon Serverless
  • PlanetScale
  • Turso, com SQLite on the edge
  • Supabase

Em nosso projeto com Neon e Drizzle no Vercel Edge, o cold start caiu de 2,5 s para 700 ms. Esse foi o resultado real.

Quando usar

O Drizzle não serve para tudo, mas se destaca nestes cenários:

1. Aplicações serverless

Se a aplicação roda em Lambda ou Edge Functions, o pacote leve e o cold start rápido do Drizzle são vantagens fundamentais.

2. Cenários sensíveis a desempenho

Aplicações em tempo real, sistemas financeiros e plataformas de análise de dados se beneficiam diretamente do design sem abstrações adicionais.

3. Projetos que exigem controle sobre SQL complexo

Quando há muitas consultas avançadas ou necessidade de otimização manual, a API semelhante a SQL é mais flexível que a DSL do Prisma.

4. Projetos frontend sensíveis ao tamanho do bundle

Alguns frameworks full stack, como SolidStart e Qwik, podem empacotar o código do ORM no cliente. Nesses casos, os 7,4 kB do Drizzle fazem uma diferença enorme.

Por outro lado, se a equipe conhece pouco SQL, precisa criar um protótipo rapidamente ou prefere o conjunto de ferramentas do Prisma — Prisma Studio, Prisma Migrate e Prisma Pulse —, o Prisma ainda pode ser a melhor escolha.

Configuração prática de Next.js com Drizzle

Depois da teoria, vamos configurar do zero um projeto com Next.js 15, Drizzle e PostgreSQL.

Preparação do ambiente

Crie primeiro o projeto Next.js:

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

Instale as dependências do Drizzle:

npm install drizzle-orm drizzle-kit
npm install @neondatabase/serverless  # Para usar o banco Neon
# ou
npm install postgres  # Para um PostgreSQL tradicional

Recomendo o Neon, um PostgreSQL serverless que combina muito bem com o Drizzle. Crie uma conta gratuita, abra um banco e copie a string de conexão.

Defina o schema do banco de dados

Crie db/schema.ts com a estrutura das tabelas:

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

// Tabela de usuários
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow(),
});

// Tabela de posts
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(),
});

// Relação de um para muitos
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

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

É apenas TypeScript. Não existe um arquivo de schema especial como no Prisma e não é necessário gerar um Client.

Configure a conexão com o banco

Crie db/index.ts:

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

// Lê a conexão do banco a partir da variável de ambiente
const sql = neon(process.env.DATABASE_URL!);

// Cria a instância do Drizzle
export const db = drizzle(sql, { schema });

Depois, adicione a variável a .env.local:

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

Configure o Drizzle Kit para migrações

Crie 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!,
  },
});

Gere os arquivos de migração:

npx drizzle-kit generate

O comando cria as migrações SQL no diretório drizzle/. Revise o SQL gerado e, se estiver correto, execute:

npx drizzle-kit push

As tabelas do banco estarão prontas.

Estrutura final do projeto

my-drizzle-app/
├── app/                  # Aplicação Next.js
│   ├── page.tsx
│   └── actions.ts        # Server Actions
├── db/
│   ├── schema.ts         # Tabelas e relações
│   └── index.ts          # Conexão com o banco
├── drizzle/
│   └── migrations/       # Migrações geradas automaticamente
├── drizzle.config.ts     # Configuração do Drizzle Kit
├── .env.local            # Variáveis de ambiente
└── package.json

O processo inteiro leva menos de cinco minutos. Não há uma sintaxe especial como Prisma Schema nem uma espera por prisma generate: é TypeScript direto.

API semelhante a SQL do Drizzle na prática

Com tudo configurado, vamos ver as operações mais comuns e o uso em Server Actions do Next.js.

Operações CRUD básicas

Consulta de dados (SELECT)

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

// Consulta todos os usuários
const allUsers = await db.select().from(users);

// Consulta um usuário
const user = await db.select().from(users).where(eq(users.id, 1));

// Busca aproximada
const result = await db.select().from(users).where(like(users.name, '%John%'));

// Condições compostas
const admins = await db
  .select()
  .from(users)
  .where(
    and(
      eq(users.role, 'admin'),
      gt(users.createdAt, new Date('2024-01-01'))
    )
  );

// Ordenação e limite
const latestPosts = await db
  .select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(10);

A lógica é a mesma do SQL, expressa por chamadas de função.

Inserção de dados (INSERT)

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

// Insere vários registros
await db.insert(users).values([
  { name: 'Alice', email: '[email protected]' },
  { name: 'Bob', email: '[email protected]' },
]);

// Retorna o registro inserido
const [newUser] = await db
  .insert(users)
  .values({ name: 'Charlie', email: '[email protected]' })
  .returning();

console.log(newUser.id); // ID gerado automaticamente

Atualização de dados (UPDATE)

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

// Atualiza vários registros
await db
  .update(posts)
  .set({ published: true })
  .where(eq(posts.authorId, 1));

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

Exclusão de dados (DELETE)

// Exclui um registro
await db.delete(users).where(eq(users.id, 1));

// Exclui vários registros
await db.delete(posts).where(eq(posts.published, false));

// Retorna os dados excluídos
const deleted = await db
  .delete(users)
  .where(eq(users.id, 1))
  .returning();

Consultas avançadas

Operações JOIN

// LEFT JOIN: consulta usuários e seus 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: consulta apenas usuários que têm posts
const activeAuthors = await db
  .select()
  .from(users)
  .innerJoin(posts, eq(users.id, posts.authorId));

Subconsultas

// Consulta usuários com mais de cinco 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));

Funções de agregação

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

// Conta o total de usuários
const [{ total }] = await db
  .select({ total: count() })
  .from(users);

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

Uso em Server Actions do Next.js

Crie app/actions.ts:

'use server';

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

// Cria um usuário
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: 'Não foi possível criar o usuário' };
  }
}

// Obtém todos os usuários
export async function getUsers() {
  return await db.select().from(users);
}

// Exclui um usuário
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: 'Não foi possível excluir o usuário' };
  }
}

Use a action na página:

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

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

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

Segurança de tipos no TypeScript

É aqui que o Drizzle realmente se destaca: a inferência completa de tipos.

// O tipo de retorno é inferido automaticamente
const users = await db.select().from(users);
// Tipo: { id: number; name: string; email: string; createdAt: Date }[]

// Ao escolher campos, o tipo também é inferido
const result = await db
  .select({
    id: users.id,
    name: users.name,
  })
  .from(users);
// Tipo: { id: number; name: string }[]

// O erro é detectado na compilação
await db.select().from(users).where(eq(users.id, '1'));
// ❌ Erro do TypeScript: string não pode ser atribuído a number

O IntelliSense sugere campos, funções e operadores de condição disponíveis. Em muitos casos, basta digitar um ponto para ver tudo o que pode ser usado.

Você não precisa decorar a API nem consultar a documentação o tempo todo: o compilador TypeScript atua como uma documentação interativa.

Drizzle vs. Prisma: comparação detalhada

Até aqui falamos muito das vantagens do Drizzle. Para escolher com objetividade, precisamos comparar os dois lados.

Comparação de desempenho

~7,4 kB
Bundle do Drizzle
contra 14 MB do Prisma
~600 ms
Cold start
contra 2,5 s do Prisma
0
Dependências em runtime
TypeScript puro
Source: Dados de monitoramento em produção
CritérioDrizzlePrisma v5Prisma v7Observação
Tamanho do bundle~7,4 kB~14 MB~1 MBDrizzle é o mais leve
Cold start~600 ms~2,5 s~1,5 sEm ambiente serverless
Dependências em runtime0Binário Rust0Drizzle e Prisma v7 não têm dependências
Uso de memória~5 MB~80 MB~30 MB, estimadoMemória real em runtime
Velocidade da verificação de tiposRápidaMédiaMédiaInferência mais simples no Drizzle

Dados de um caso real

Depois que nossa equipe migrou um projeto do Prisma v5 para o Drizzle:

  • A primeira requisição caiu de 3 s para 700 ms, uma melhora de 76%.
  • O bundle de produção caiu de 18 MB para 4 MB, uma redução de 78%.
  • O cold start do Lambda caiu de 2,4 s para 650 ms, ficando 73% mais rápido.

Esses números vieram do monitoramento real em produção, não de um benchmark artificial.

Comparação da experiência de desenvolvimento

Como o schema é definido

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

// Prisma, com DSL própria
model User {
  id   Int    @id @default(autoincrement())
  name String
}

Vantagem do Drizzle: é TypeScript, tem bom suporte da IDE e permite usar recursos como tipos condicionais e generics.

Vantagem do Prisma: o Prisma Schema é mais conciso e fácil de ler.

Estilo da API de consulta

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

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

Vantagem do Drizzle: por ficar mais próximo de SQL, expressa consultas complexas com mais facilidade.

Vantagem do Prisma: é mais amigável para quem conhece pouco SQL, e a semântica das consultas aninhadas é mais clara.

Comparação de recursos

Vantagens do Drizzle

  1. Extremamente leve: o núcleo de 7,4 kB é ideal para projetos sensíveis ao tamanho do bundle.
  2. Nativo para serverless: cold start rápido e compatibilidade com ambientes Edge.
  3. Controle sobre SQL: maior flexibilidade em consultas complexas e otimização.
  4. Sem gerar Client: altere o schema e use imediatamente, sem prisma generate.
  5. Tree-shakable: apenas o código utilizado entra no bundle.

Vantagens do Prisma

  1. Ecossistema maduro: lançado em 2021, tem uma comunidade ampla e muitos tutoriais.
  2. Ferramentas completas: Prisma Studio para administração visual, Prisma Migrate para migrações e Prisma Pulse para atualizações em tempo real.
  3. Consultas relacionais inteligentes: trata o problema de N+1 automaticamente e torna consultas aninhadas mais diretas.
  4. Amigável para iniciantes: não exige conhecimento profundo de SQL, e a DSL tem uma curva de aprendizado suave.
  5. Mensagens de erro detalhadas: os erros em runtime são mais fáceis de entender.

Recomendações para escolher

Escolha Drizzle quando

✅ O projeto roda em Vercel, Cloudflare Workers, Deno Deploy ou outro ambiente serverless ou Edge.
✅ O desempenho é crítico, como em aplicações em tempo real, sistemas financeiros e APIs de alta concorrência.
✅ O bundle precisa ficar abaixo de 1 MB.
✅ A equipe domina SQL e prefere controlar as consultas.
✅ O projeto exige otimização SQL complexa.

Escolha Prisma quando

✅ A equipe conhece pouco SQL e precisa começar rapidamente.
✅ A experiência de desenvolvimento e a cadeia de ferramentas são prioridades.
✅ É necessário administrar o banco visualmente com Prisma Studio.
✅ O projeto tem consultas relacionais e modelagem de dados complexas.
✅ A aplicação não roda em serverless e o tamanho do bundle não é crítico.

Minha recomendação pessoal

Em um projeto novo, eu escolheria assim:

  • Projeto pessoal ou startup: Drizzle, pelo desempenho, custo baixo e potencial de otimização.
  • Projeto empresarial ou em equipe: depende da experiência do time. Se todos dominam SQL, Drizzle; caso contrário, Prisma.
  • Serverless-first: Drizzle sem hesitação.
  • Servidor tradicional: ambos funcionam; o Prisma oferece uma cadeia de ferramentas mais completa.

Também existe uma abordagem híbrida: usar Drizzle nos módulos centrais sensíveis a desempenho e Prisma no painel administrativo. Os dois podem coexistir sem conflito.

Guia de migração e boas práticas

Migração do Prisma para o Drizzle

Se você já tem um projeto com Prisma e quer experimentar o Drizzle, pode migrar gradualmente.

Etapa 1: converter o schema

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

// Conversão para Drizzle Schema
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow(),
});

Etapa 2: reescrever as consultas

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

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

Etapa 3: substituir aos poucos

Prisma e Drizzle podem coexistir:

// Use Drizzle em consultas sensíveis a desempenho
import { db } from '@/db/drizzle';
const latestPosts = await db
  .select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(50);

// Mantenha temporariamente o Prisma em consultas relacionais complexas
import { prisma } from '@/db/prisma';
const userWithRelations = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: { include: { comments: { include: { author: true } } } },
  },
});

Substitua uma parte de cada vez para reduzir o risco.

Boas práticas com Drizzle

1. Gerenciamento do pool de conexões

Em serverless, cuide especialmente do pool:

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

// Configura o cache de conexões
neonConfig.fetchConnectionCache = true;

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

2. Consultas preparadas

Consultas executadas com frequência podem ser preparadas para melhorar o desempenho:

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

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

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

3. Transações

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

  // Cria o post relacionado
  await tx.insert(posts).values({
    title: 'First Post',
    authorId: user.id,
  });

  // Se qualquer etapa falhar, toda a transação será revertida
});

4. Reutilização de tipos

Exporte os tipos inferidos para o frontend:

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

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

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

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

5. Tratamento de erros

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

try {
  await db.insert(users).values({
    name: 'John',
    email: '[email protected]',
  });
} catch (error) {
  // Código de erro do PostgreSQL
  if (error.code === '23505') {
    console.error('O e-mail já existe');
  } else {
    console.error('Erro no banco de dados', error);
  }
}

Conclusão

Depois de tudo isso, vale resumir.

O Drizzle ORM é uma alternativa diferente no ecossistema de ORMs para TypeScript: em vez de esconder SQL, ele o adota. Seu núcleo de 7,4 kB, a ausência de dependências em runtime e o desempenho próximo ao SQL nativo mostram que ser leve não é abrir mão de recursos, mas escolher uma abordagem melhor.

Ele não substitui o Prisma em todos os casos. O Prisma tem um ecossistema mais maduro, ferramentas mais completas e uma experiência mais amigável para iniciantes. Mas o Drizzle pode ser a opção mais adequada quando o projeto:

  • Roda em ambiente serverless.
  • É sensível a desempenho e tamanho do bundle.
  • Exige controle sobre SQL complexo.
  • É desenvolvido por uma equipe com boa base de SQL.

Os dados da nossa equipe são claros: após migrar do Prisma para o Drizzle, o cold start ficou 73% mais rápido, o bundle diminuiu 78% e a primeira requisição caiu de três segundos para 700 ms. Isso não é uma micro-otimização; é uma mudança de patamar.

Se os cold starts e bundles grandes do Prisma estão causando problemas, experimente o Drizzle. A curva de aprendizado é curta: quem conhece SQL consegue começar em dez minutos.

Alguns links úteis:

Experimente. Talvez, assim como eu, você não queira voltar atrás.

Configuração e migração completas de Next.js com Drizzle ORM

Configure o Drizzle ORM do zero, desde o ambiente, o schema e a conexão até as consultas e a migração gradual do Prisma

Estimated time: PT2H

  1. 1

    Step 1: Primeira etapa: prepare o ambiente e instale as dependências

    Crie o projeto Next.js:
  2. 2

    Step 2: Segunda etapa: defina o schema do banco

    Crie db/schema.ts e defina as tabelas:
  3. 3

    Step 3: Terceira etapa: configure a conexão e o Drizzle Kit

    Crie db/index.ts:
  4. 4

    Step 4: Quarta etapa: consulte com a API semelhante a SQL

    Operações CRUD básicas:
  5. 5

    Step 5: Quinta etapa: use em Server Actions do Next.js

    Crie app/actions.ts:
  6. 6

    Step 6: {user.name}

    {user.email}
  7. 7

    Step 7: Sexta etapa: migre do Prisma para o Drizzle, opcional

    Se o projeto já usa Prisma, faça uma migração gradual.

FAQ

Qual é a diferença entre Drizzle ORM e Prisma? Quando devo escolher o Drizzle?
Principais diferenças:

Desempenho:
• Drizzle: 7,4 kB, cold start de 600 ms e nenhuma dependência em runtime
• Prisma v5: 14 MB, cold start de 2,5 s e binário Rust
• Prisma v7: 1 MB e cold start de 1,5 s; melhorou, mas ainda é mais lento que o Drizzle

Experiência de desenvolvimento:
• Drizzle: API semelhante a SQL, familiar para quem conhece SQL e sem geração de Client
• Prisma: DSL própria, que exige aprendizado e a execução de prisma generate

Escolha Drizzle quando:
✅ O projeto roda em Vercel, Cloudflare Workers, Deno Deploy ou outro ambiente serverless ou Edge
✅ O desempenho é crítico, como em aplicações em tempo real, sistemas financeiros e APIs de alta concorrência
✅ Há limite de bundle inferior a 1 MB
✅ A equipe domina SQL e prefere controlar as consultas diretamente
✅ O projeto exige otimizações SQL complexas

Escolha Prisma quando:
✅ A equipe não conhece bem SQL e precisa começar rápido
✅ A experiência de desenvolvimento e uma cadeia de ferramentas completa são prioridades
✅ É necessário administrar o banco visualmente com Prisma Studio
✅ Há consultas relacionais e modelagem de dados complexas
✅ O projeto não é serverless e o tamanho do bundle não é crítico
O que significa dizer que a API do Drizzle ORM é semelhante a SQL?
A API do Drizzle faz o código TypeScript seguir de perto a sintaxe SQL.

Exemplo em SQL:

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

O equivalente em Drizzle:

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

Características principais:
• A estrutura do código é praticamente igual à do SQL
• Quem conhece SQL entende a consulta rapidamente
• A curva de aprendizado é quase nula para quem já usa SQL
• Há inferência completa de tipos e preenchimento automático no IntelliSense
• Erros são detectados durante a compilação

Em comparação, o Prisma usa uma DSL própria:
```typescript
await prisma.user.findMany({
include: {
posts: {
where: { views: { gt: 1000 } },
},
},
});
```

Como o Drizzle fica mais próximo do SQL nativo, consultas complexas são mais fáceis de expressar.
Qual é a vantagem de desempenho do Drizzle ORM em ambientes serverless?
Dados de um caso real de migração do Prisma v5 para o Drizzle:

Melhorias de desempenho:
• Primeira requisição: de 3 s para 700 ms, 76% mais rápida
• Bundle de produção: de 18 MB para 4 MB, redução de 78%
• Cold start do Lambda: de 2,4 s para 650 ms, 73% mais rápido

Por quê?

1. Tamanho do bundle:
• O pacote principal do Drizzle ocupa apenas 7,4 kB com minificação e gzip
• O Client gerado pelo Prisma v5 pode chegar a 14 MB
• Em ambientes com limite de 1 MB, como Cloudflare Workers, o Prisma ultrapassa o teto

2. Cold start:
• Na inicialização, o Prisma precisa analisar uma enorme string DMMF, Data Model Meta Format
• Em um schema de porte médio, ela pode passar de seis milhões de caracteres
• Essa análise acontece a cada cold start e atrasa a inicialização
• O Drizzle não tem camada de abstração em runtime: a consulta é traduzida diretamente para SQL

3. Uso de memória:
• Drizzle: cerca de 5 MB
• Prisma v5: cerca de 80 MB
• Prisma v7: cerca de 30 MB, valor estimado

Esses números vêm do monitoramento de um ambiente real de produção, não de um benchmark artificial.
Como migrar do Prisma para o Drizzle? O processo é complicado?
A migração pode ser gradual, sem substituir tudo de uma vez.

Primeiro, converta o schema:
• Transforme os models do Prisma em definições pgTable
• Mapeie Int para serial ou integer, String para text e DateTime para timestamp
• Converta relações do Prisma para a função relations do Drizzle

Depois, reescreva as consultas:
• findUnique e findMany viram select().from().where()
• include vira leftJoin ou innerJoin
• create vira insert().values()
• update vira update().set().where()

Prisma e Drizzle podem coexistir durante a transição:
```typescript
// Use Drizzle em consultas sensíveis a desempenho
import { db } from '@/db/drizzle';
const latestPosts = await db
.select()
.from(posts)
.orderBy(desc(posts.createdAt))
.limit(50);

// Mantenha temporariamente o Prisma em relações complexas
import { prisma } from '@/db/prisma';
const userWithRelations = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: { include: { comments: { include: { author: true } } } },
},
});
```

Substitua as consultas aos poucos para reduzir o risco. O trabalho é principalmente uma conversão de sintaxe.
Quais bancos de dados o Drizzle ORM aceita e em quais ambientes ele roda?
Bancos de dados compatíveis:

• PostgreSQL, com recomendação para Neon Serverless
• MySQL
• SQLite, incluindo Turso e SQLite on the edge
• SQL Server

Ambientes serverless compatíveis:

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

Drivers serverless com suporte nativo:

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

Em nosso projeto, usamos Neon com Drizzle no Vercel Edge e reduzimos o cold start de 2,5 s para 700 ms.

Vantagens principais:
• Compatibilidade com os ambientes serverless mais usados
• Suporte nativo a drivers de banco serverless
• Nenhuma dependência em runtime, permitindo a execução em qualquer ambiente JavaScript
Como funciona a segurança de tipos no Drizzle ORM em comparação com o Prisma?
O Drizzle oferece segurança completa de tipos com um modelo mais simples.

Inferência de tipos:

```typescript
// O tipo de retorno é inferido automaticamente
const users = await db.select().from(users);
// Tipo: { id: number; name: string; email: string; createdAt: Date }[]

// Ao selecionar campos, o tipo também é inferido
const result = await db
.select({
id: users.id,
name: users.name,
})
.from(users);
// Tipo: { id: number; name: string }[]

// O erro é detectado durante a compilação
await db.select().from(users).where(eq(users.id, '1'));
// ❌ Erro do TypeScript: string não pode ser atribuído a number
```

Características principais:
• Não é preciso gerar um Client; o TypeScript infere os tipos diretamente
• O IntelliSense completa campos, funções e operadores de condição
• Problemas aparecem na compilação, não apenas em runtime
• A verificação de tipos é rápida porque a inferência do Drizzle é mais simples

Em comparação com o Prisma:
• O Prisma exige prisma generate para criar o Client
• Em projetos grandes, a inferência do Prisma pode deixar a compilação TypeScript mais lenta
• A inferência mais simples do Drizzle tende a compilar mais rápido

Os tipos inferidos podem ser exportados para o frontend:
```typescript
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
```
O Drizzle ORM é adequado para iniciantes? Como é a curva de aprendizado?
Para quem conhece SQL, a curva de aprendizado do Drizzle é bastante suave.

Tempo para começar:
• Quem conhece SQL: cerca de dez minutos
• Quem não conhece SQL: primeiro precisa aprender os fundamentos

Por que é fácil aprender:
• A estrutura da API é quase igual à do SQL
• Não há uma nova DSL para aprender, como no Prisma
• O IntelliSense oferece dicas completas de tipos e preenchimento automático
• O compilador TypeScript funciona como uma documentação prática

Exemplos:
```typescript
// SELECT
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))
```

Quem já usa SQL reconhece essas operações sem consultar a documentação.

Em comparação com o Prisma:
• O Prisma exige o aprendizado de uma DSL própria
• Ele é mais amigável para quem não conhece SQL
• Para quem já domina SQL, o Drizzle é mais direto

Recomendação:
• Se a equipe conhece SQL, escolha Drizzle diretamente
• Se a equipe não conhece SQL, use Prisma ou aprenda os fundamentos antes de adotar Drizzle

19 min de leitura · Publicado em: 20 dez 2025 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog