Migrations no TypeORM: Versionamento Seguro de Banco de Dados
Como usar o CLI do TypeORM para gerenciar migrations. Diferença entre migration:generate e migration:create, CI/CD para bancos de dados, reversibilidade (método down) e a regra de ouro do versionamento imutável.
Executar um ALTER TABLE diretamente pelo DBeaver ou PgAdmin pode funcionar localmente, mas é o caminho garantido para o desastre no deploy: a aplicação espera uma coluna que não existe em produção e falha. Migrations tratam o banco de dados como código versionado: toda alteração de esquema (criar tabela, alterar coluna, adicionar índice) é descrita em um arquivo TypeScript executado sequencialmente.
Migration: Generate vs Create
O TypeORM oferece duas formas de criar migrations. A mais produtiva é a geração automática (migration:generate), onde o TypeORM compara suas Entidades (código) com o estado atual do banco e gera o SQL necessário automaticamente. A forma manual (migration:create) é para alterações que não derivam de uma entidade: seeds de dados, atualizações de dados existentes ou índices complexos que o gerador não sabe criar sozinho.
# 1. Gera automaticamente baseada na diferença (Entidades vs Banco)
npx typeorm-ts-node-commonjs migration:generate src/infra/database/migrations/CreateUsersTable -d src/infra/database/data-source.ts
# 2. Cria uma migration vazia (para preencher manualmente com SQL customizado)
npx typeorm-ts-node-commonjs migration:create src/infra/database/migrations/SeedDefaultAdminimport type { MigrationInterface, QueryRunner } from 'typeorm';
export class CreateUsers1689230104821 implements MigrationInterface {
name = 'CreateUsers1689230104821';
// Método UP: Avança a migration (o que deve ser feito)
public async up(queryRunner: QueryRunner): Promise<void> {
// Usando SQL puro é geralmente mais seguro para geradas automaticamente
await queryRunner.query(`
CREATE TABLE "users" (
"id" uuid NOT NULL DEFAULT uuid_generate_v4(),
"name" varchar NOT NULL,
"email" varchar NOT NULL,
"password" varchar NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "UQ_97672ac88f789774dd47f708be3" UNIQUE ("email"),
CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id")
)
`);
}
// Método DOWN: Reverte a migration (como desfazer a alteração)
// Essencial para o caso do deploy quebrar e precisarmos de rollback
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "users"`);
}
}Executando e Controlando Estado
O TypeORM cria uma tabela de metadados chamada migrations no seu banco que registra o nome (com timestamp) de cada migration executada. O timestamp no nome do arquivo é a chave de ordenamento: migrations são executadas sempre em ordem cronológica. A regra de ouro: nunca edite uma migration que já foi executada em produção. Se precisa corrigir, crie uma nova migration. Editar uma existente causa dessincronização entre o estado do banco e o registro da tabela migrations.
# Roda todas as migrations pendentes (que ainda não estão na tabela migrations)
npx typeorm-ts-node-commonjs migration:run -d src/infra/database/data-source.ts
# Desfaz apenas a ÚLTIMA migration executada (roda o método down)
npx typeorm-ts-node-commonjs migration:revert -d src/infra/database/data-source.tsA Regra de Ouro da Imutabilidade: Nunca edite o arquivo de uma migration que já foi commitada e compartilhada com a equipe (e principalmente as que já foram para produção). Se você esqueceu uma coluna, não edite a CreateUsers; rode um migration:generate criando a AddAvatarToUsers. Se você alterar um arquivo antigo, os bancos da equipe ficarão corrompidos em relação ao hash do arquivo.
Migrations em CI/CD
Em produção, as migrations devem rodar de forma automatizada no pipeline de Deploy, antes da nova versão do backend inicializar. Se a migration falhar (ex: erro de SQL), o deploy aborta e o servidor antigo continua rodando, garantindo zero-downtime.
steps:
- name: Instalar dependências
run: npm ci
- name: Build do TypeScript
run: npm run build
- name: Executar Migrations em Produção
run: npm run typeorm migration:run
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
- name: Reiniciar PM2/Servidor
run: pm2 reload apiConclusão
O uso de Migrations traz paz de espírito. Quando um dev júnior entrar na equipe, ele apenas rodará migration:run e terá a exata mesma estrutura de banco que o ambiente de Produção. A sincronização manual (synchronize: true do TypeORM) só deve ser usada em projetos rasos de laboratório — em produção, o controle rígido das migrations é inegociável.