2

Versionamento de Schema com TypeORM: Migrations do Desenvolvimento ao Deploy

O erro que destrói tabelas em produção

Uma migration com synchronize: true em produção apaga colunas. Não é bug do TypeORM: é comportamento documentado. O synchronize compara as entities do código com o schema atual e executa ALTER TABLE para alinhar os dois. Se você removeu uma propriedade da entity (ou renomeou), o synchronize interpreta como "drop column". Sem confirmação, sem rollback, sem aviso.

Esse é o ponto de partida deste post: migrations existem para que mudanças de schema sejam intencionais, rastreáveis e reversíveis. O TypeORM oferece tooling para isso, mas a configuração padrão empurra você na direção errada.

Configuração base do DataSource

O TypeORM 0.3+ substituiu ormconfig.json pelo DataSource. A configuração abaixo funciona para PostgreSQL e separa o comportamento por ambiente:

// src/data-source.ts
import { DataSource } from "typeorm";
import { join } from "path";

// Migrations ficam em pasta separada das entities
// para que o CLI encontre os arquivos compilados em produção
const isProduction = process.env.NODE_ENV === "production";

export const AppDataSource = new DataSource({
  type: "postgres",
  host: process.env.DB_HOST ?? "localhost",
  port: Number(process.env.DB_PORT ?? 5432),
  username: process.env.DB_USER ?? "app",
  password: process.env.DB_PASS ?? "secret",
  database: process.env.DB_NAME ?? "myapp",

  // synchronize NUNCA true em produção
  synchronize: false,

  // logging de queries só em dev para diagnóstico
  logging: isProduction ? ["error", "migration"] : true,

  entities: [join(__dirname, "entities", "*.{ts,js}")],
  migrations: [join(__dirname, "migrations", "*.{ts,js}")],
});

O synchronize: false é a única configuração segura para qualquer ambiente que persista dados reais. Em desenvolvimento, o fluxo correto é gerar migrations a partir das mudanças nas entities.

Gerando migrations a partir das entities

O CLI do TypeORM compara as entities registradas no DataSource com o schema atual do banco e gera o SQL necessário. Para que isso funcione, o banco de desenvolvimento precisa estar rodando e acessível.

Primeiro, configure o script no package.json:

{
  "scripts": {
    "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
    "migration:generate": "npm run typeorm -- migration:generate -d src/data-source.ts",
    "migration:run": "npm run typeorm -- migration:run -d src/data-source.ts",
    "migration:revert": "npm run typeorm -- migration:revert -d src/data-source.ts",
    "migration:create": "npm run typeorm -- migration:create"
  }
}

Com uma entity definida:

// src/entities/Product.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from "typeorm";

@Entity("products")
export class Product {
  @PrimaryGeneratedColumn("uuid")
  id: string;

  @Column({ type: "varchar", length: 255 })
  name: string;

  @Column({ type: "integer" })
  priceInCents: number;

  // Coluna nullable porque produtos existentes não terão SKU preenchido
  @Column({ type: "varchar", length: 50, nullable: true })
  sku: string | null;

  @CreateDateColumn({ type: "timestamptz" })
  createdAt: Date;
}

Gere a migration:

npm run migration:generate -- src/migrations/CreateProducts

O TypeORM cria um arquivo com timestamp e o SQL inferido. O resultado se parece com isto:

// src/migrations/1719432000000-CreateProducts.ts
import { MigrationInterface, QueryRunner } from "typeorm";

export class CreateProducts1719432000000 implements MigrationInterface {
  name = "CreateProducts1719432000000";

  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE "products" (
        "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
        "name" character varying(255) NOT NULL,
        "priceInCents" integer NOT NULL,
        "sku" character varying(50),
        "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
        CONSTRAINT "PK_products_

---

Leia o artigo completo em [https://www.vivodecodigo.com.br/backend/typeorm-migrations-versionamento-schema-desenvolvimento-deploy](https://www.vivodecodigo.com.br/backend/typeorm-migrations-versionamento-schema-desenvolvimento-deploy)
Carregando publicação patrocinada...
1

Meus 2 cents de dica, eu utilizo muito em diversos projetos dos meus clientes o sistema de multitenancy com multischema, e depois de apanhar bastante hoje em dia eu consigo trocar de schemas via user.currentTenantViewId etc e injetando via requisições de maneira que o meu repositório consiga trabalhar com base no jwt saber qual tenant eu preciso acessar e se tenho permissões respeitando segurança e isolamento sem precisar do RLS, fica facil de dar manutenção, e se um cliente quiser os dados ou eu apagar todo o histórico dele não sofro com queries mt complexas, meio que codo pra um com garantia q baterá no tenant correto

link de um pequeno exemplo q fiz no começo: https://github.com/JulioVianaDev/multitenancy-postgresl-nestjs

ps: uso hj em dia o sistema da atlassian de 1 database per client mas schemas tbm funcionam bem caso se interesse mais links de referencia

https://www.atlassian.com/trust/reliability/cloud-architecture-and-operational-practices#multi-tenant-architecture

https://blog.bytebytego.com/p/how-atlassian-migrated-4-million