Ir para o conteúdo
Backend

Migrações de Banco de Dados com PostgreSQL: Prisma Migrate vs Drizzle Kit

Marcos Soares
Atualizado em 
13 minutos de leitura
Ilustracao 3D de dois monolitos cristalinos opostos representando Prisma Migrate vs Drizzle Kit para migracoes PostgreSQL
Ouça este artigo
0:00Migrações de Banco de Dados com PostgreSQL: Prisma Migrate vs Drizzle Kit--:--

Conteúdo técnico toda semana

Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.

Sem spam. Cancele a qualquer momento com 1 clique.

Neste artigo

O problema que migração resolve (e o que ela cria)

Alterar a estrutura de um banco PostgreSQL em produção sem um sistema de migrações é como editar código direto no servidor: funciona até o dia que não funciona. Migrações versionam o schema do banco da mesma forma que Git versiona o código. Cada alteração vira um arquivo rastreável, reversível e aplicável em qualquer ambiente.

A questão prática é: qual ferramenta usar? Prisma Migrate e Drizzle Kit resolvem o mesmo problema com filosofias opostas. Prisma gera SQL a partir de um schema declarativo próprio. Drizzle gera SQL a partir de definições TypeScript que espelham a estrutura real do banco. A escolha entre eles afeta como você escreve queries, como faz deploy e como lida com migrações que dão errado.

Se você já trabalha com Prisma no Fastify, este post vai expandir o que você sabe sobre o lado de migrações. Se está avaliando Drizzle, vai sair daqui com critérios concretos para decidir.

Setup: do zero à primeira migração

Prisma Migrate

Bash
# Instala Prisma como dependência de desenvolvimento
npm install prisma --save-dev
npm install @prisma/client
 
# Inicializa o diretório prisma/ com schema.prisma e .env
npx prisma init --datasource-provider postgresql

O arquivo schema.prisma é o ponto central. Toda alteração de schema passa por ele:

Prisma
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}
 
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
 
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
 
  // @map renomeia a coluna no banco sem mudar o nome no TypeScript
  @@map("users")
}
 
model Post {
  id        Int      @id @default(autoincrement())
  title     String   @db.VarChar(255)
  content   String?  @db.Text
  published Boolean  @default(false)
  authorId  Int      @map("author_id")
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now()) @map("created_at")
 
  @@index([authorId])
  @@map("posts")
}
Bash
# Gera o SQL de migração e aplica no banco de desenvolvimento
npx prisma migrate dev --name create_users_and_posts

Esse comando faz três coisas: gera um arquivo SQL em prisma/migrations/, aplica no banco e regenera o Prisma Client.

Drizzle Kit

Bash
npm install drizzle-orm pg
npm install drizzle-kit --save-dev
npm install @types/pg --save-dev

No Drizzle, o schema é TypeScript puro:

TYPESCRIPT
// src/db/schema.ts
import { pgTable, serial, varchar, text, boolean, integer, timestamp } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
 
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  name: varchar("name", { length: 255 }),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
 
export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 255 }).notNull(),
  content: text("content"),
  published: boolean("published").default(false).notNull(),
  // Referência explícita à tabela users
  authorId: integer("author_id").notNull().references(() => users.id),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});
 
// Relations são separadas da definição de tabela
// Isso mantém o schema da tabela puro e as queries relacionais opcionais
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));
 
export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));
TYPESCRIPT
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
 
export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle/migrations",
  dialect: "postgresql",
  dbCredentials: {
    // Usa variável de ambiente diretamente, sem .env mágico
    url: process.env.DATABASE_URL!,
  },
});
Bash
# Gera o SQL de migração comparando schema atual com estado do banco
npx drizzle-kit generate
 
# Aplica migrações pendentes
npx drizzle-kit migrate

Comparação operacional: onde cada um brilha

CritérioPrisma MigrateDrizzle Kit
Linguagem do schemaPrisma Schema Language (DSL própria)TypeScript puro
Geração de SQLAutomática, baseada em diff do schemaAutomática, baseada em diff do schema
SQL editável antes de aplicarSim, arquivo .sql gerado pode ser editadoSim, arquivo .sql gerado pode ser editado
Migração customizada (data migration)Editar o SQL gerado manualmenteEditar o SQL gerado ou criar migration vazia
Rollback nativoNão tem rollback automático (precisa criar migração reversa)Não tem rollback automático (mesma abordagem)
Introspecção de banco existenteprisma db pulldrizzle-kit pull
Type safety nas queriesPrisma Client geradoInferência direta do schema TypeScript
Overhead de runtimePrisma Engine (binário Rust, ~15MB)Zero: traduz para SQL no build, driver nativo
Curva de aprendizadoMaior (DSL própria + conceitos Prisma)Menor para quem conhece SQL

A diferença de arquitetura de runtime é significativa. O Prisma Client depende de um query engine binário que roda como sidecar do processo Node.js. O Drizzle gera SQL diretamente e usa o driver pg nativo. Em ambientes com restrição de tamanho de bundle (como Edge Functions), o Drizzle tem vantagem clara. O Prisma tem um adapter para edge, mas com limitações.

Workflow de desenvolvimento: adicionando uma coluna

Cenário concreto: adicionar um campo bio na tabela users.

Com Prisma

Edite o schema.prisma:

Prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  bio       String?  @db.Text  // Nova coluna
  posts     Post[]
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")
 
  @@map("users")
}
Bash
npx prisma migrate dev --name add_bio_to_users

O arquivo gerado em prisma/migrations/<timestamp>_add_bio_to_users/migration.sql:

SQL
-- AlterTable
ALTER TABLE "users" ADD COLUMN "bio" TEXT;

Com Drizzle

Edite o schema.ts:

TYPESCRIPT
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  name: varchar("name", { length: 255 }),
  bio: text("bio"), // Nova coluna
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
Bash
npx drizzle-kit generate

O resultado é equivalente: um arquivo SQL com ALTER TABLE. A diferença está no que acontece ao redor. O Prisma regenera o client automaticamente. No Drizzle, o tipo já está atualizado porque o schema é TypeScript: a inferência pega a mudança no próximo import.

O que NÃO fazer

Anti-pattern 1: renomear coluna sem cuidado

Tanto Prisma quanto Drizzle interpretam "remover coluna A e criar coluna B" quando você renomeia. Isso destrói dados.

Errado (Prisma):

Prisma
// ANTES
model User {
  name String?
}
 
// DEPOIS: mudou "name" para "displayName"
model User {
  displayName String? @map("display_name")
}

O prisma migrate dev vai gerar:

SQL
-- PERIGOSO: DROP + ADD, dados perdidos
ALTER TABLE "users" DROP COLUMN "name";
ALTER TABLE "users" ADD COLUMN "display_name" TEXT;

Correto: edite o SQL gerado antes de aplicar, ou crie a migração em dois passos.

SQL
-- Renomeia sem perder dados
ALTER TABLE "users" RENAME COLUMN "name" TO "display_name";

O Drizzle Kit tem o mesmo comportamento. Ambas as ferramentas pedem que você revise o SQL gerado antes de aplicar em produção. Se você não lê o SQL, vai perder dados. Sem exceção.

Para estratégias seguras de alteração de colunas em produção, o post sobre zero-downtime migrations cobre o assunto em profundidade.

Anti-pattern 2: rodar prisma migrate dev em produção

O comando migrate dev é exclusivo para desenvolvimento. Ele reseta o banco se detecta drift (diferença entre o estado esperado e o real). Em produção:

Bash
# ERRADO: pode dropar e recriar tabelas
npx prisma migrate dev
 
# CORRETO: aplica apenas migrações pendentes, falha se houver drift
npx prisma migrate deploy

No Drizzle, o drizzle-kit push (que sincroniza schema direto no banco sem gerar arquivo) tem o mesmo risco. Use drizzle-kit migrate em produção, que aplica apenas os arquivos SQL versionados.

Anti-pattern 3: ignorar migrações de dados

Ferramentas de migração de schema não resolvem migração de dados. Se você adiciona uma coluna NOT NULL sem default em uma tabela com registros existentes, a migração falha.

SQL
-- FALHA: coluna NOT NULL sem default em tabela com dados
ALTER TABLE "users" ADD COLUMN "role" VARCHAR(50) NOT NULL;
 
-- CORRETO: adiciona com default, atualiza, remove default se necessário
ALTER TABLE "users" ADD COLUMN "role" VARCHAR(50) NOT NULL DEFAULT 'member';

Quando a migração envolve transformação de dados existentes (split de coluna, merge de tabelas), escreva o SQL manualmente dentro do arquivo de migração. Nenhuma das duas ferramentas gera data migrations automaticamente.

Para cenários onde precisa reverter migrações sem perder dados, o post sobre reverter migrations com segurança detalha estratégias práticas.

Deploy em produção: CI/CD pipeline

A migração em produção deve rodar antes do deploy da aplicação, nunca durante o boot. Um exemplo com GitHub Actions:

YAML
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
 
jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
 
      - run: npm ci
 
      # Para Prisma:
      - run: npx prisma migrate deploy
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
 
      # OU para Drizzle:
      # - run: npx drizzle-kit migrate
      #   env:
      #     DATABASE_URL: ${{ secrets.DATABASE_URL }}
 
  deploy:
    needs: migrate  # Só deploya se a migração passou
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ... passos de deploy da aplicação

Separar a etapa de migração do deploy da aplicação permite que, se a migração falhar, o deploy não aconteça. Se a aplicação falhar no deploy, o banco já está no estado correto para a próxima tentativa. Esse padrão se integra com estratégias de deploy como blue-green, onde o novo schema precisa ser compatível com a versão antiga da aplicação durante a transição.

Quando escolher Prisma, quando escolher Drizzle

A decisão depende de três fatores concretos:

Escolha Prisma Migrate quando:

  • O time tem devs com pouca experiência em SQL e se beneficia de uma abstração mais opinativa
  • Você já usa Prisma Client e quer manter uma ferramenta só para schema + queries + migrações
  • O projeto usa multitenancy com Prisma e você quer manter consistência no tooling
  • Precisa de introspecção de banco legado para gerar o schema inicial (prisma db pull é maduro)

Escolha Drizzle Kit quando:

  • O time é confortável com SQL e quer controle fino sobre as queries geradas
  • O deploy alvo inclui edge runtimes ou ambientes com restrição de bundle size
  • Você quer type safety sem camada de abstração intermediária (sem query engine binário)
  • O projeto precisa de queries SQL complexas (CTEs, window functions, subqueries laterais) que o Prisma abstrai com dificuldade

Nenhum dos dois resolve:

  • Migrações que exigem coordenação entre múltiplos serviços (use um orquestrador dedicado)
  • Schema changes que precisam de processamento assíncrono pesado para migrar dados em background
  • Rollback automático de dados (ambos versionam schema, não dados)

Introspecção: partindo de um banco existente

Se você já tem um PostgreSQL rodando e quer adotar uma dessas ferramentas, ambas suportam introspecção:

Bash
# Prisma: gera schema.prisma a partir do banco existente
npx prisma db pull
 
# Drizzle: gera schema TypeScript a partir do banco existente
npx drizzle-kit pull

O Prisma gera o schema.prisma completo, incluindo relações inferidas das foreign keys. O Drizzle gera arquivos TypeScript com a definição das tabelas. Em ambos os casos, revise o resultado: nomes de relações inferidos nem sempre fazem sentido semântico, e constraints complexas (check constraints, partial indexes) podem não ser capturadas completamente.

Para projetos que usam Supabase como backend, a introspecção é o caminho para trazer o schema existente do Supabase para dentro do versionamento local.

Checklist de migração segura

  1. Leia o SQL gerado antes de aplicar. Sempre.
  2. Teste a migração em um dump do banco de produção, não em banco vazio.
  3. Migrações que adicionam NOT NULL sem default falham em tabelas com dados.
  4. Renomear coluna gera DROP + ADD. Edite o SQL para usar RENAME COLUMN.
  5. Separe migração de schema de migração de dados.
  6. Em produção, use migrate deploy (Prisma) ou migrate (Drizzle), nunca os comandos de desenvolvimento.
  7. Rode migrações antes do deploy da aplicação, não durante o boot.

FAQ

Posso usar Prisma Migrate e Drizzle no mesmo projeto?

Tecnicamente sim, mas é uma péssima ideia. Cada ferramenta mantém sua própria tabela de controle de migrações (_prisma_migrations e __drizzle_migrations). Ter duas fontes de verdade para o estado do schema gera conflitos inevitáveis. Escolha uma.

O Prisma Migrate suporta rollback automático?

Não. O Prisma não gera migrações reversas. Se uma migração precisa ser desfeita, você cria uma nova migração que reverte as alterações manualmente. O Drizzle Kit tem a mesma limitação. Para estratégias de rollback seguro, veja o post sobre reverter migrations sem perder dados.

Como lidar com migrações em branches diferentes do Git?

Ambas as ferramentas geram arquivos com timestamp no nome. Se dois devs criam migrações em branches paralelas, os arquivos não conflitam no merge (nomes diferentes), mas o schema final pode ser inconsistente. A prática que funciona: após o merge, rode prisma migrate dev ou drizzle-kit generate para verificar se o estado do schema está correto. Se houver divergência, gere uma migração de reconciliação.

Drizzle Kit funciona com schemas PostgreSQL separados (não o public)?

Sim. No schema TypeScript, use pgSchema para definir schemas customizados:

TYPESCRIPT
import { pgSchema, serial, varchar } from "drizzle-orm/pg-core";
 
// Define um schema PostgreSQL separado do public
const tenantSchema = pgSchema("tenant_001");
 
export const users = tenantSchema.table("users", {
  id: serial("id").primaryKey(),
  email: varchar("email", { length: 255 }).notNull(),
});

Qual é o impacto de performance do Prisma Engine vs Drizzle em queries?

O overhead do Prisma Engine (o binário Rust que traduz queries) é mensurável mas raramente é gargalo. Em benchmarks sintéticos, Drizzle é mais rápido por não ter a camada intermediária. Na prática, se sua aplicação faz menos de 1000 queries por segundo, a diferença é irrelevante. Se faz mais, o gargalo provavelmente está no PostgreSQL, não no ORM. Otimize índices e queries antes de trocar de ferramenta por performance.

Posição: Drizzle é a escolha mais segura para projetos novos em 2025

Prisma democratizou o acesso a bancos de dados para desenvolvedores TypeScript, e o Prisma Client continua sendo a melhor experiência de autocompletion para quem não quer pensar em SQL. Mas o custo dessa abstração (binário de runtime, DSL própria, lock-in no ecossistema) pesa mais do que o benefício quando o time conhece SQL.

Drizzle Kit produz migrações previsíveis porque o schema é TypeScript que mapeia 1:1 para SQL. Não há tradução de DSL intermediária. Quando algo dá errado, você debugga SQL, não a interpretação que o Prisma fez do seu schema. Para projetos que usam Clean Architecture, o Drizzle se encaixa melhor como detalhe de infraestrutura que não vaza para as camadas superiores.

Dito isso, se seu time já usa Prisma e está produtivo, migrar para Drizzle por purismo técnico é desperdício. A melhor ferramenta de migração é a que todo mundo no time sabe operar com segurança.

Marcos Soares

Escrito por

Marcos Soares

Fullstack Developer · CEO da Agência Poti

Fullstack Developer e CEO da Agência Poti. Mais de 20 anos construindo arquiteturas cloud-native com React, Next.js e sistemas distribuídos. Parceiro comercial do estúdio iellou design. Fundador do Vivo de Código.

Comentários

Participe da discussão

Seja o primeiro a comentar!

Continue Aprofundando

Guias de integração relacionados

Conteúdo técnico toda semana

Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.

Sem spam. Cancele a qualquer momento com 1 clique.