As migrações de banco de dados são modificações feitas em um banco de dados. Essas modificações podem incluir a alteração do esquema de uma tabela, a atualização dos dados em um conjunto de registros, a inserção de dados ou a exclusão de uma faixa de registros.

As migrações de banco de dados geralmente são executadas antes que um aplicativo comece e não são executadas com sucesso mais de uma vez para o mesmo banco de dados. Ferramentas de migração de banco de dados salvam um histórico de migrações que foram executadas em um banco de dados para que possam ser rastreadas para fins futuros.

Neste artigo, você aprenderá como configurar e executar migrações de banco de dados em uma aplicação mínima de API Node.js. Usaremos ts-migrate-mongoose e um script npm para criar uma migração e inserir dados em um banco de dados MongoDB. O ts-migrate-mongoose suporta a execução de scripts de migração a partir de código TypeScript, bem como de código CommonJS.

O ts-migrate-mongoose é um framework de migração para projetos Node.js que utilizam mongoose como o mapeador objeto-dados. Ele fornece um modelo para escrever scripts de migração. Também fornece uma configuração para executar os scripts programaticamente e a partir da CLI.

Tabela de Conteúdos

Como configurar o projeto

Para utilizar o ts-migrate-mongoose para migrações de banco de dados, você precisa ter o seguinte:

  1. Um projeto Node.js com o mongoose instalado como dependência.

  2. Um banco de dados MongoDB conectado ao projeto.

  3. O MongoDB Compass (Opcional – para nos permitir visualizar as alterações no banco de dados).

Foi criado um repositório inicial que pode ser clonado de ts-migrate-mongoose-starter-repo para facilitar. Clone o repositório, preencha as variáveis de ambiente e inicie a aplicação executando o comando npm start.

Acesse http://localhost:8000 com um navegador ou um cliente de API como o Postman e o servidor retornará um texto “Olá!” para mostrar que a aplicação inicial está funcionando como esperado.

Como configurar o ts-migrate-mongoose para o projeto

Para configurar o ts-migrate-mongoose para o projeto, instale o ts-migrate-mongoose com este comando:

npm install ts-migrate-mongoose

O ts-migrate-mongoose permite a configuração com um arquivo JSON, um arquivo TypeScript, um arquivo .env ou via CLI. É aconselhável usar um arquivo .env porque o conteúdo da configuração pode conter uma senha de banco de dados e não é adequado torná-lo público. Os arquivos .env geralmente são ocultados por arquivos .gitignore para que sejam mais seguros de usar. Este projeto usará um arquivo .env para a configuração do ts-migrate-mongoose.

O arquivo deve conter as seguintes chaves e seus valores:

  • MIGRATE_MONGO_URI – o URI do banco de dados Mongo. É o mesmo que a URL do banco de dados.

  • MIGRATE_MONGO_COLLECTION – o nome da coleção (ou tabela) na qual as migrações devem ser salvas. O valor padrão é migrations, que é o que é usado neste projeto. O ts-migrate-mongoose salva as migrações no MongoDB.

  • MIGRATE_MIGRATIONS_PATH – o caminho para a pasta de armazenamento e leitura dos scripts de migração. O valor padrão é ./migrations, que é o que é usado neste projeto.

Como Seedar Dados de Usuário com o ts-migrate-mongoose

Conseguimos criar um projeto e conectá-lo com sucesso a um banco de dados Mongo. Neste ponto, queremos seedar dados de usuário no banco de dados. Precisamos:

  1. Criar uma coleção (ou tabela) de usuários

  2. Usar o ts-migrate-mongoose para criar um script de migração para seedar dados

  3. Usar o ts-migrate-mongoose para rodar a migração e seedar os dados de usuário no banco de dados antes que a aplicação inicie

1. Crie uma coleção de usuários usando o Mongoose

O esquema do Mongoose pode ser usado para criar uma coleção de usuários (ou tabela). Documentos de usuário (ou registros) terão os seguintes campos (ou colunas): email, favouriteEmoji e yearOfBirth.

Para criar um esquema do Mongoose para a coleção de usuários, crie um arquivo user.model.js na raiz do projeto contendo o seguinte trecho de código:

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema(
  {
    email: {
      type: String,
      lowercase: true,
      required: true,
    },
    favouriteEmoji: {
      type: String,
      required: true,
    },
    yearOfBirth: {
      type: Number,
      required: true,
    },
  },
  {
    timestamps: true,
  }
);

module.exports.UserModel = mongoose.model("User", userSchema);

2. Crie um Script de Migração com ts-migrate-mongoose

O ts-migrate-mongoose fornece comandos de CLI que podem ser usados para criar scripts de migração.

Ao executar npx migrate create <nome-do-script> na pasta raiz do projeto, será criado um script na pasta MIGRATE_MIGRATIONS_PATH (./migrations no nosso caso). <nome-do-script> é o nome que queremos que o arquivo do script de migração tenha quando for criado.

Para criar um script de migração para inserir dados de usuário, execute:

npx migrate create seed-users

O comando irá criar um arquivo na pasta ./migrations com um nome no formato –<timestamp>-seed-users.ts. O arquivo terá o seguinte conteúdo do trecho de código:

// Importe seus modelos aqui

export async function up (): Promise<void> {
  // Escreva a migração aqui
}

export async function down (): Promise<void> {
  // Escreva a migração aqui
}

A função up é usada para executar a migração. A função down é usada para reverter o que quer que a função up execute, se necessário. No nosso caso, estamos tentando inserir usuários no banco de dados. A função up conterá o código para inserir usuários no banco de dados e a função down conterá o código para excluir usuários criados na função up.

Se o banco de dados for inspecionado com o MongoDB Compass, a coleção de migrações terá um documento que se parece com isso:

{
  "_id": ObjectId("6744740465519c3bd9c1a7d1"),
  "name": "seed-users",
  "state": "down",
  "createdAt": 2024-11-25T12:56:36.316+00:00,
  "updatedAt": 2024-11-25T12:56:36.316+00:00,
  "__v": 0
}

O campo state do documento de migração é definido como down. Após ser executado com sucesso, ele muda para up.

Você pode atualizar o código em ./migrations/<timestamp>-seed-users.ts para o código no trecho abaixo:

require("dotenv").config() // carregar variáveis de ambiente
const db = require("../db.js")
const { UserModel } = require("../user.model.js");

const seedUsers = [
  { email: "[email protected]", favouriteEmoji: "🏃", yearOfBirth: 1997 },
  { email: "[email protected]", favouriteEmoji: "🍏", yearOfBirth: 1998 },
];

export async function up (): Promise<void> {
  await db.connect(process.env.MONGO_URI)
  await UserModel.create(seedUsers);}

export async function down (): Promise<void> {
  await db.connect(process.env.MONGO_URI)
  await UserModel.delete({
    email: {
      $in: seedUsers.map((u) => u.email),
    },
  });
}

3. Execute a Migração Antes de Iniciar a Aplicação

O ts-migrate-mongoose nos fornece comandos de CLI para executar a função up e down dos scripts de migração.

Com npx migrate up <nome-do-script> podemos executar a função up de um script específico. Com npx migrate up podemos executar a função up de todos os scripts na pasta ./migrations com um state de down no banco de dados.

Para executar a migração antes do início da aplicação, fazemos uso de scripts npm. Scripts npm com um prefixo de pre serão executados antes de um script sem o prefixo pre. Por exemplo, se houver um script dev e um script predev, sempre que o script dev for executado com npm run dev, o script predev será executado automaticamente antes do script dev.

Vamos usar essa funcionalidade de scripts npm para colocar o comando ts-migrate-mongoose em um script prestart para que a migração seja executada antes do script start.

Atualize o arquivo package.json para ter um script prestart que execute o comando ts-migrate-mongoose para executar a função up dos scripts de migração no projeto.

  "scripts": {
    "prestart": "npx migrate up",
    "start": "node index.js"
  },

Com essa configuração, quando npm run start é executado para iniciar a aplicação, o script prestart será executado para executar a migração usando ts-migrate-mongoose e popular o banco de dados antes do início da aplicação.

Deve ter algo semelhante ao trecho abaixo após executar npm run start:

Synchronizing database with file system migrations...
MongoDB connection successful
up: 1732543529744-seed-users.ts 
All migrations finished successfully

> [email protected] start
> node index.js

MongoDB connection successful                      
Server listening on port 8000

Confira o branch seed-users do repositório para ver o status atual da base de código neste ponto do artigo.

Como Construir um Endpoint de API para Buscar Dados Populados

Podemos construir um endpoint de API para buscar os dados dos usuários populados em nosso banco de dados. No arquivo server.js, atualize o código para o que está no trecho abaixo:

const { UserModel } = require("./user.model.js")

module.exports = async function (req, res) {
  const users = await UserModel.find({}) // buscar todos os usuários no banco de dados

  res.writeHead(200, { "Content-Type": "application/json" });
  return res.end(JSON.stringify({ // retornar uma representação JSON dos dados dos usuários buscados
    users: users.map((u) => ({
      email: u.email,
      favouriteEmoji: u.favouriteEmoji,
      yearOfBirth: u.yearOfBirth,
      createdAt: u.createdAt
    }))
  }, null, 2));
};

Se iniciarmos a aplicação e visitarmos http://localhost:8000 usando o Postman ou um navegador, obteremos uma resposta JSON semelhante à mostrada abaixo:

{
  "users": [
    {
      "email": "[email protected]",
      "favouriteEmoji": "🏃",
      "yearOfBirth": 1997,
      "createdAt": "2024-11-25T14:18:55.416Z"
    },
    {
      "email": "[email protected]",
      "favouriteEmoji": "🍏",
      "yearOfBirth": 1998,
      "createdAt": "2024-11-25T14:18:55.416Z"
    }
  ]
}

Observe que se a aplicação for executada novamente, o script de migração não será executado novamente porque o estado da migração agora será up após ter sido executado com sucesso.

Confira a branch fetch-users do repositório para ver o status atual da base de código neste ponto do artigo.

Conclusão

As migrações são úteis ao construir aplicações e há necessidade de inserir dados iniciais para testes, inserir usuários administrativos, atualizar o esquema do banco de dados adicionando ou removendo colunas e atualizando os valores das colunas em muitos registros de uma vez.

O ts-migrate-mongoose pode ajudar a fornecer um framework para executar migrações em suas aplicações Node.js se você usar o Mongoose com o MongoDB.