Introdução a ORM no Node.js com sequelize parte 2

Tecnologia

No artigo anterior configuramos nosso projeto, agora chegou a hora de configurar nossa conexão com o sequelize.

Primeiro precisamos criar um arquivo na raiz do projeto chamado .sequelizerc, este será responsável por mapear cada pasta que o sequelize vai usar de acordo com nossa estrutura de pastas.

  • ./.sequelizerc
const { resolve } = require('path');
 
module.exports = {
  config: resolve(__dirname, 'src', 'config', 'database.js'),
  'models-path': resolve(__dirname, 'src', 'app', 'models'),
  'migrations-path': resolve(__dirname, 'src', 'database', 'migrations'),
  'seeders-path': resolve(__dirname, 'src', 'database', 'seeds'),
}

E agora vamos começar a criar alguns arquivos importantes para que o sequelize gere a parte de migrations, seeds e também possa conectar com a instância do banco de dados.

  • src/config/database.js
module.exports = {
    dialect: 'mysql',
    host: 'seu host',
    username: 'usuario',
    password: 'senha',
    database: 'sequelize-example',
    define: {
      timestamps: false,
      underscored: true,
      underscoredAll: true,
    },
}

OBS: Neste arquivo temos uma particularidade, ele não está usando o import/export , infelizmente o sequelize-cli  roda usando o commonJS então esse arquivo de configuração necessita estar em  commonJS

  • src/database/index.js
import Sequelize from 'sequelize';
import databaseConfig from '../config/database';
const models = [];
 
class Database {
  constructor(){
      this.init();
  }
 
  init(){
    this.connection = new Sequelize(databaseConfig);
    models.map((model) => model.init(this.connection))
      .map((model) => {
          if(model.associate) model.associate(this.connection.models);
          return model;
      })
  }
}
 
export default new Database();

Antes de prosseguir precisamos instalar uma  dependência chamada mysql2 para isso:

yarn add mysql2

Para prosseguir precisamos entender mais alguns conceitos, que são o migrations e seeds.

  • Migrations: São classes que executaram promises capazes de gerar nossa estrutura na base de dados, ele irá  gerar as tabelas, relacionamentos e  campos por etapas, para cada nova tabela teremos uma nova migration, não precisa se preocupar o  migrate sabe identificar qual foi a última executada.
  • Seeds: São classes que executam promises capazes de gerar dados padrões para as tabelas, seja para mockar ou gerar dados padrões para tabelas.

Para criar uma migration basta rodarmos o seguinte comando:

yarn sequelize migration:generate --name create-table-aluno

Se  tudo correr bem  e o sequelize conseguir conectar com o banco você receberá a seguinte mensagem: 

E perceberá que na pasta src/database/migrations  e haverá um novo arquivo e abriremos ele para configurar a tabela  de alunos e vamos seguir a seguinte configuração:

'use strict';
 
module.exports = {
  up: (queryInterface, Sequelize) => queryInterface.createTable("aluno",{
    id:{
      type: Sequelize.INTEGER,
      allowNull:false,
      autoIncrement: true,
      primaryKey:true
    },
    nome:{
      type: Sequelize.STRING(100),
      allowNull:false
    },
    dtnascimento: {
      type: Sequelize.DATE,
      allowNull: false
    },
    telefone:{
      type: Sequelize.STRING(20),
      allowNull: true
    },
    bairro: {
      type: Sequelize.STRING(100),
      allowNull: true
    },
    cep: {
      type: Sequelize.STRING(20),
      allowNull: true
    },
  }),
 
  down: (queryInterface) => queryInterface.dropTable("aluno")
};
  • up: Responsável por executar  uma criação/alteração de tabela
  • down: Responsável por executar métodos de desfazer alterações como por exemplo , se o up está criando a tabela aluno, no down  colocamos seu drop, pois  é o comando que será  executado ao  reverter a migration já executada.

Agora que criamos nossa migration de aluno, faremos o mesmo processo para o curso:

yarn sequelize migration:generate --name create-table-curso

E assim que for criado a nossa migration de curso iremos criar sua classe igual fizemos com a de usuário, estabelecendo o up e o down baseado no nosso escopo de banco de dados apresentado anteriormente.

  • src/database/migrations/xxxxx.create-table-curso.js
'use strict';
 
module.exports = {
  up: (queryInterface, Sequelize) => queryInterface.createTable("curso", {
    id: {
      type: Sequelize.INTEGER,
      primaryKey:true,
      autoIncrement: true,
      allowNull:false
    },
    nomecurso: {
      type: Sequelize.STRING(100),
    },
    duracaocurso: Sequelize.STRING(100),
    datapublicacao: Sequelize.DATE
  }),
 
  down: (queryInterface) => queryInterface.dropTable("curso")
};

Agora a única alteração que faltou colocarmos nessa migration é o vínculo entre o curso e o aluno, uma questão que pode surgir nesse momento é porque não adicionamos o campo com relacionamento no aluno enquanto estávamos criando sua classe , bom a resposta disso é simples! como não havíamos criado a migration de curso se tivéssemos tentado rodar essas migrations receberíamos um erro dizendo que a tabela não existe, então iremos adicionar esse campo de relacionamento na nossa próxima criação de migration abaixo.

yarn sequelize migration:generate --name add-field-to-aluno

Desta vez nossa missão será adicionar um novo campo em aluno com relacionamento com curso, então nossa classe vai sofrer algumas alterações:

  • src/database/migrations/xxxxx-add-field-to-aluno.js:
'use strict';
 
module.exports = {
  up: (queryInterface, Sequelize) => queryInterface.addColumn("aluno", "codigocurso", {
    type: Sequelize.INTEGER,
    references: { model: 'curso', key: 'id'},
    onUpdate: 'CASCADE',
    onDelete: 'SET NULL',
    allowNull:true
  }),
 
  down: (queryInterface) => queryInterface.removeColumn('aluno', 'codigocurso')
};

Em references iremos dizer com qual model ele terá relacionamento e em key será qual o campo que sofrerá esse relacionamento, logo em seguida precisamos dizer o que vai acontecer ao registro quando for atualizado ou deletado com os parâmetros (onUpdate, onDelete).

Agora que criamos todas nossas migrations necessárias vamos rodá-las e ver se tudo correu bem, para isso devemos rodar o seguinte comando:

yarn sequelize db:migrate

Se tudo correr bem, teremos o seguinte resultado:

Isso significará que tudo correu bem, para que você entenda como o sequelize trabalha abra seu gerenciador de banco de dados, e vamos ver o que o sequelize criou no banco de dados que informamos para ele!

Como podemos ver após ter rodado a migration foram geradas as tabelas que criamos via classe e o sequelize criou uma tabela extra chamada sequelizemeta está pasta é muito importante para o funcionamento é através dela que o sequelize sabe qual foi a última migration executada para que na próxima vez que o comando seja executado ele continue da última migration executada.

Agora que temos nossas tabelas criadas vamos criar suas respectivas classes para que o node possa fazer inclusões através do nosso ORM, vamos começar criando o CRUD básico para curso.

Para isso iremos até a pasta de models dentro de “src/app/models e criaremos um arquivo chamado Curso.js

Powered by Rock Convert
  • src/app/models/Curso.js
import Sequelize, { Model } from "sequelize";
 
class Curso extends Model {
  static init(sequelize) {
    super.init(
      {
        nomecurso: Sequelize.STRING(100),
        duracaocurso: Sequelize.STRING(100),
        datapublicacao: Sequelize.DATE
      },
      {
        sequelize,
      }
    );
 
    return this;
  }
}
 
export default Curso

Agora temos uma classe vinculada ao sequelize, como se fosse o reflexo da nossa migration, a partir de agora ao usar a classe Curso poderemos fazer qualquer operação no banco de dados, agora vamos configurar nosso controller para iniciar as primeiras inclusões no banco de dados

Vamos criar nosso primeiro controller, mas antes precisamos colocar alguns conceitos na cabeça! utilizar a metodologia Restful e seguir a regra de não deixar muitas funções em um único controller o recomendo é no máximo 5, sendo:

  • store: Onde iremos realizar a inclusão de uma informação
  • update: Onde iremos atualizar um registro referente a esse controller
  • delete: Onde realizamos a exclusão de alguma informação
  • index: Onde realizaremos a listagem dos itens incluídos neste controller
  • show: Onde realizaremos a visualização de um registro específico
  • src/app/controller/CursoController.js
//importamos o model
import Curso from "../models/Curso";
 
class CursoController {
  async store(req, res) {
    const curso = await Curso.create(req.body);
    return res.json(curso)
  }
  async index(req, res) {
    const cursos = await Curso.findAll();
    return res.json(cursos)
  }
  async update(req, res) {
    let curso = await Curso.findByPk(req.params.id)
    curso = await curso.update(req.body)
    return res.json(curso)
  }
  async delete(req, res) {
    let curso = await Curso.findByPk(req.params.id)
    curso = await curso.destroy(req.body)
    return res.json(curso)
  }
  async show(req, res) {
    let curso = await Curso.findByPk(req.params.id)
    return res.json(curso)
  }
}
 
export default new CursoController();

Depois de criar o nosso controller precisamos registrá-lo no nosso arquivo de rotas e após esse processo já conseguimos testar nossa API.

Deixaremos nosso routes.js da seguinte forma:

  • src/routes.js
import { Router } from 'express';
import CursoController from './app/controllers/CursoController';
const routes = Router();
 
routes.get('/cursos', CursoController.index)
routes.get('/cursos/:id', CursoController.show)
routes.post('/cursos', CursoController.store)
routes.put('/cursos/:id', CursoController.update)
routes.delete('/cursos/:id', CursoController.delete)
 
export default routes;

Neste ponto todos os endpoints referente a cursos estão pronto para testar, lembrando sempre de seguir o seguinte modelo:

  • Get: Quando for uma consulta que não enviará informações no maximo uma query;
  • Post: Para realizar um processo ou incluir novos registro
  • Put: Para atualizar um registro
  • Delete: Para excluir um registro

OBS: Essa é a forma que denominamos cada método de uma requisição 

Agora seguimos para a parte de aluno! Está parte será um pouco mais complexa!  pois envolverá relacionamentos entre a tabela aluno X curso

Vamos começar criando o Model de Aluno!

  • src/app/models/Aluno.js
import Sequelize, { Model } from "sequelize";
 
class Aluno extends Model {
  static init(sequelize) {
    super.init(
      {
        nome: Sequelize.STRING(100),
        dtnascimento: Sequelize.STRING(100),
        telefone: Sequelize.DATE,
        bairro:Sequelize.STRING(100),
        cep: Sequelize.STRING(20)
      },
      {
        sequelize,
        tableName: 'aluno',
      }
    );
 
    return this;
  }
  /**
   * Sempre que um model tiver um relacionamento 
   * adicionaremos esse associate para indicar
   * com que esse model se relaciona
   */
  static associate(models){
    /**
     * Neste caso usaremos o belongsTo, mas dependendo da necessidade
     * temos outras opçoes
     * belongsToMany, belongsTo, HasMany, HasOne,Association
     * para conhecer mais acesse:
     * https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html
     */
 
    this.belongsTo(models.Curso, {
        foreignKey: 'curso_id',
        as: 'curso',
    });
 
  }
}
 
export default Aluno

OBS: Em belongsTo colocaremos o model que terá o relacionamento e um objeto indicando qual é o campo que sofre o relacionamento na tabela “Aluno” e como chamaremos esse relacionamento na hora que precisar exibir em uma consulta.

OBS²: Como adicionamos um novo model precisamos adicionar ele no nosso arquivo de database/index.js ficando da seguinte forma

  • src/database/index.js
import Sequelize from 'sequelize';
import databaseConfig from '../config/database';
import Curso from '../app/models/Curso';
import Aluno from '../app/models/Aluno';
const models = [Curso,Aluno];
 
class Database {
  constructor(){
      this.init();
  }
 
  init(){
    this.connection = new Sequelize(databaseConfig);
    models.map((model) => model.init(this.connection))
      .map((model) => {
          if(model.associate) model.associate(this.connection.models);
          return model;
      })
  }
}
 
export default new Database();

Feito isso nossa API está pronto para receber o controller de alunos e endpoints! 

Então vamos lá !

Criaremos um controller para o aluno da seguinte forma:

import Aluno from "../models/Aluno";
import Curso from "../models/Curso";
 
class AlunoController {
  async store(req, res) {
    const aluno = await Aluno.create(req.body);
    return res.json(aluno)
  }
  async index(req, res) {
    const alunos = await Aluno.findAll({
        attributes: [
            'id','nome', 'dtnascimento', 'telefone', 'bairro', 'cep'
        ],
        include: [
            { 
                model: Curso,
                as: 'curso'
            }
        ]
    });
    return res.json(alunos)
  }
  async update(req, res) {
    let aluno = await Aluno.findByPk(req.params.id)
    aluno = await aluno.update(req.body)
    return res.json(aluno)
  }
  async delete(req, res) {
    let aluno = await Aluno.findByPk(req.params.id)
    aluno = await aluno.destroy(req.body)
    return res.json(aluno)
  }
  async show(req, res) {
    let aluno = await Aluno.findByPk(req.params.id, {
        attributes: [
            'id','nome', 'dtnascimento', 'telefone', 'bairro', 'cep'
        ],
        include: [
            { 
                model: Curso,
                as: 'curso'
            }
        ]
    })
    return res.json(aluno)
  }
}
 
export default new AlunoController();

OBS: Nesse controller temos algo novo, dentro dos métodos do model que realiza conexão do banco de dados, agora incluímos algumas opções no objeto vamos conhecê-los: 

  • attributes: Indicamos quais campos queremos que seja retornado
  • include: Inserimos quais models estão relacionados com esse para exibir no retorno da consulta 

Exemplo do retorno

Bom agora que já temos nosso model e controllers criados precisamos adicioná-los para nossas rotas!

  • src/routes.js
import { Router } from 'express';
import CursoController from './app/controllers/CursoController';
import AlunoController from './app/controllers/AlunoController';
const routes = Router();
 
routes.get('/cursos', CursoController.index)
routes.get('/cursos/:id', CursoController.show)
routes.post('/cursos', CursoController.store)
routes.put('/cursos/:id', CursoController.update)
routes.delete('/cursos/:id', CursoController.delete)
 
routes.get('/alunos', AlunoController.index)
routes.get('/alunos/:id', AlunoController.show)
routes.post('/alunos', AlunoController.store)
routes.put('/alunos/:id', AlunoController.update)
routes.delete('/alunos/:id', AlunoController.delete)
 
export default routes;

E assim concluímos nossa API e conseguimos aprender alguns conceitos de organização de projetos, uso do sequelize

Se quiser consultar esse código estará disponível no github! 

https://github.com/aylonMuramatsu

Referências:

https://sequelize.org/