Migrations e Seeders no SequelizeJS

Migrations e Seeders no SequelizeJS

Vamos começar com um overview

O sequelize é um Object Relational Mapper (ORM) para NodeJS baseado em promises que tem como principal objetivo unir a orientação objeto com a parte relacional, onde faz um mapeamento dos tipos de dados relacional gerando o objeto com a mesma representação dos dados.

Dando suporte para os bancos já conhecidos, como: Postgres, MySQL, MariaDB, SQLite e Microsoft SQL Server. Atualmente uma grande parte dos desenvolvendores estão utilizando ORMs em seus projetos devido ao ganho de produtividade que eles lhes proporcionam dando suporte nas criações de querys SQL para inserir, alterar e remover dados, gerenciando também nos controles de transações, relacionamentos e muito mais…

Migrations

O banco de dados relacional é uma coleção de dados que já tem uma estrutra de relacionamento predefinidos com uma organização de linhas e colunas.

Vamos imaginar que estamos trabalhando em uma equipe com um banco de dados relacional e por algum motivo mais de uma pessoa está utilizando a mesma tabela. Para adequar a uma necessidade que surgiu um deles precisa que o telefone do usuário na tabela seja representado como um inteiro e não uma string, então ele dá um push em desenvolvimento e quebra a atividade do companheiro de equipe.

“Mas ele vai receber um erro…”

Sim, entretanto terá que descobrir ou pergunta a equipe o que foi alterado e ir no seu próprio ambiente de desenvolvimento para fazer a mesma alteração.

Percebe o quanto isso pode quebrar a produtividade quando falamos em trabalho em equipe? É exatamente por isso que as migrations foram criadas para ser um controle de versionamento de um estado para outro dos bancos de dados, assim como o GIT é para código da aplicação.

A construção das migrations

O Sequelize CLI ajuda a gerenciar no versionamento das migrations devido a criação dos seus arquivos terem uma sequência de timestamps onde gera uma espécie de histórico. Vamos criar a nossa primeira migration.

yarn sequelize migration:create --name=create-users

O próximo passo é estruturar nossa tabela como um objeto informando seus atributos e definindo suas regras…

module.exports = {
  up: (queryInterface, Sequelize) => {
    const UsersTable = queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      fullName: {
        allowNull: false,
        type: Sequelize.STRING,
      },
      email: {
        allowNull: false,
        unique: true,
        type: Sequelize.STRING,
      },
      password: {
        allowNull: false,
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: new Date(),
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: new Date(),
      },
    });

    return UsersTable;
  },

  down: queryInterface => queryInterface.dropTable('Users'),
};

Comando para rodar nossa(s) migration(s):

yarn sequelize db:migrate

Caso você esteja criando uma nova migration e já rodou o comando para gerar a tabela users, quando você emitir novamente o comando ele só irá rodar a migration que não está no banco por conta que quando foi rodado a primeira vez ele gerou uma tabela de controle chamada SequelizeMeta.

Primeira dica: a depender como foi definido pela equipe para o projeto se vai utilizar camelcase ou underscored siga exatamente como foi definido em toda a parte do projeto desde a criação das tabelas até as models, para evitar conflitos na construção das querys com o ORM pois o código SQL é gerado com base no model e se for definido que na migration “full_name” e na model “fullName” haverá conflitos.

Segunda dica: evite construir tabelas no singular pois você deixa essa responsabilidade para o Sequelize por no plural e nomes como “permanencia” vai ser gerado para eles como “permanecium” causando conflitos na geração do código SQL.

“mas não tem como eu decidir se quero ou não que ele gere minhas tabelas no singular ou plural?”

Tem, veja no gist abaixo todas as propriedades da tabela.

const User = sequelize.define('User', { 
  /* .... */ 
}, {
  // não adicionar os atributos (updatedAt, createdAt)
  timestamps: false,

  // não permite deletar do banco, mas inseri na coluna deletedAt a data da exclusão
  // se o timestamps estiver ativado
  paranoid: true,

  // não adiciona camelcase para atributos gerados automaticamente
  // então se definirmos updatedAt ele será criado como updated_at
  underscored: true,

  // para evitar que o sequelize defina suas tabelas com o nome em plural automaticamente
  // como permanencia para permanencium ative a opção como true
  freezeTableName: true,

  // definindo o nome da sua tabela
  tableName: 'user_project'
});

Outros comandos para migrations

Revertendo as modificações mais recentes.

yarn sequelize db:migrate:undo

Podendo desfazer para o estado inicial.

yarn sequelize db:migrate:undo:all

Ou pode reverter somente uma migration específica.

yarn sequelize db:migrate:undo:all --to XXXXXXXXXXXXXX-create-users.js

Logo abaixo tem uma imagem sobre os tipos de dados que o Sequelize trabalha e suas especificações…

[https://cdn.hashnode.com/res/hashnode/image/upload/v1599743238349/XsMsMfujA.html](https://github.com/diomalta/FireNote)https://github.com/diomalta/FireNote

Vamos falar de algumas propriedades para os campos que serão criados, no gist abaixo mostra exemplos do que podemos utilizar na construção dos atributos.

  // é o mesmo conceito para o banco de dados, um campo definido como chave primaria
  // não pode ter dois ou mais registros de mesmo valor e também não pode ter valor nulo
  primaryKey: true,

  // habilita que o banco crie a coluna como auto incremente 1, 2, ..., n+1
  // registro 1, registro 2...
  autoIncrement: true,

  // o tipo de dados que vai ser armazenando no campo
  type: Sequelize.INTEGER,

  // definido como false um campo não pode ser criado como nulo, se for como true pode
  // ser criado como nulo
  allowNull: false,

  // garante que um valor inserido só estará uma única vez na tabela
  unique: true,

   // pode definir também o nome do erro e a mensagem que o banco irá retornar caso tentem
   // inserir um valor já existente
   unique: {
    name: 'users_email',
    msg: 'Ops, infelizmente esse email já foi cadastrado...',
  },

  // caso você escreveu o nome do atributo como "fullName" em camelcase
  // mas na criação da tabela quer que ele fique com underscored 
  field: 'full_name',

  // forma de validar o dados que está sendo inserido no campo
  // pode utilizar já pronta pelo sequelize ou criar sua própria regex
  validate: {
    notEmpty: true,
    isEmail: true,
    is: /regex_validation/
  },

  // definindo um valor inicial quando for criado aquele campo na tabela
  defaultValue: new Date(),

  // criando uma chave estrangeira referênciando uma chave primária
  // de uma outra tabela
  references: { model: 'anotation', key: 'id' },

  // vamos usar o termo pai e filho
  // quando o pai é deletado, nenhuma filho sem pai deve permanecer ativa na tabela filho. 
  // Todos os filhos daquele pai são excluídos também. 
  // utilizado em um atribute de chave estrangeira
  onUpdate: 'CASCADE',
  onDelete: 'CASCADE',

Seeders

A equipe está trabalhando com testes (Ou tem dados como default para a plicação) e toda vez ela precisa inserir dados é um processo manual atráves de uma controller, ou utilizando o model para fazer a criação, a depender da quantidade de registros que queira inserir será muito trabalhoso.

Por isso os seeders existem, para deixar um padrão de preenchimento das tabelas com informações reais ou fictícias a depender da necessidade para ser utilizado no momento que desejar.

Construindo Seeders

yarn sequelize seed:generate --name users
module.exports = {
  up: (queryInterface, Sequelize) => queryInterface.bulkInsert('Users', 
    [
      {
        fullName: 'John Doe',
        email: 'test@medium.com',
        password: '9ff7b641722c30acdc058f2499d97dd8',
      },
      {
        fullName: 'John Travolta',
        email: 'test2@medium.com',
        password: '082b66a712e3efe31385f3158e057496',
      }
    ], {}),

  down: (queryInterface) => queryInterface.bulkDelete('Users', null, {}),
};

Desfazendo dos seeders mais recentes.

yarn sequelize db:seed:undo

Reverter um seed específico.

yarn sequelize db:seed:undo --seed name-of-seed-as-in-data

Se desfazendo de todos a seeders gerados até o presente momento.

yarn sequelize db:seed:undo:all

Conclusão

Utilizando o Sequelize no seu projeto você garante que toda a sua equipe esteja sempre com a estrutura do banco de dados atualizada com facilidade atráves das migrations e com a seeders garatimos que teremos uma massa de dados feita para povoar nosso banco assim que criamos a base de dados seja em desenvolvimento ou em produção.