Sequelize - A solução para seus relacionamentos!

Nesse artigo focaremos em como fazer relacionamentos entre tabelas, usando Sequelize ORM e Node.js.

Sequelize - A solução para seus relacionamentos!
Photo by Blake Connally / Unsplash

1 - Introdução

Se você está aqui, provavelmente você já conhece essa fácil e dinâmica biblioteca ORM.

Caso não conheça, não se preocupe! Nesse artigo eu explico de forma bem tranquila o que é um CRUD e nesse video um passo-a-passo de como fazer um CRUD usando Sequelize, se você for mais de artigos do que videos esse aqui é para você!

Nesse artigo focaremos em como fazer relacionamentos entre tabelas, usando Node.JS e Sequelize, então bora!

2 - Tipos de relacionamento

O Sequelize é compatível com as associações padrão:

  • 1 para 1
  • 1 para N
  • N para N

Os métodos de criação de relacionamentos são:

  • hasOne (tem um)
  • belongsTo (pertence a)
  • hasMany (tem muitos)
  • belongsToMany (pertence a muitos)

3 - Relacionamento de 1:1 (Eu tenho um 😍 - eu pertenço a um 😍)

Vamos usar as seguintes tabelas de exemplo:

Nesse caso, cada Pessoa pode ter somente um Crusher (um caso comum onde as pessoas preferem ser mais conservadoras em seu relacionamento).

Nesse caso o model de Persons, devera ser dessa maneira:

module.exports = (sequelize, DataTypes) => {
  const Person = sequelize.define('Person', {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    name: DataTypes.STRING,
    surname: DataTypes.STRING,
  },
  {
    timestamps: false,
    tableName: 'Persons',
  });

  Person.associate = (models) => {
    Person.hasOne(models.Crusher,
      { foreignKey: 'person_id', as: 'crushers' });
  };

  return Person;
};

A função Person.associate = () => {} ira guardar as associações desse model, nesse caso a nossa tabela Pessoa possui uma Crush, referenciado pela foreignKey person_id e que deve ser chamado de crushers.

Agora no model Crush fazemos o caminho contrário:

module.exports = (sequelize, DataTypes) => {
  const Crush = sequelize.define('Crush', {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    name: DataTypes.STRING,
    surname: DataTypes.STRING,
    crusher_id: {type: DataTypes.INTEGER, foreignKey: true}
  },
  {
    timestamps: false,
    tableName: 'Crushs',
  });

  Crush.associate = (models) => {
    Crush.belongsTo(models.Person,
      { foreignKey: 'person_id', as: 'persons' });
  };

  return Crush;
};

Agora estamos declarando que Crush pertence a uma Pessoa!

Podemos testar nosso relacionamento da seguinte forma, com Express e Node.js (caso você não saiba iniciar uma aplicação no Node.js com Express veja este video)

No seu arquivo index.js insira o seguinte código e rode a aplicação:

const express = require('express');
const { Person, Crusher } = require('./models');

const app = express();

app.get('/persons', async (_req, res) => {
  try {
    const persons = await Person.findAll({
      include: { model: Crusher, as: 'crushers' },
    });

    return res.status(200).json(persons);
  } catch (e) {
    console.log(e.message);
    res.status(500).json({ message: 'Ocorreu um erro' });
  };
});

const PORT = 3000;
app.listen(PORT, () => console.log(`Ouvindo na porta ${PORT}`));

Dessa vez estamos adicionando o campo include que dirá ao Sequelize qual a configuração da associação, chamando o Model necessário e o ‘as:’ assim como declaramos na criação desse Model.

Agora fica fácil!

4 - Relacionamento de 1:N (Eu + os contatinhos 😍😍😍 + eles são só meus! 😠)

Uma modalidade de relacionamento também conhecida como, modalidade MC Catra (ou para os mais novinhos, modalidade ‘Oh Juliana o que tu qué de mim?'), onde no caso uma pessoa pode ter vários crushes.

Vamos alterar o Model Person para nosso cliente, possessivo.

module.exports = (sequelize, DataTypes) => {
  const Person = sequelize.define('Person', {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    name: DataTypes.STRING,
    surname: DataTypes.STRING,
  },
  {
    timestamps: false,
    tableName: 'Persons',
  });

  Person.associate = (models) => {
    Person.hasMany(models.Crusher,
      { foreignKey: 'person_id', as: 'crushers' });
  };

  return Person;
};

Agora uma pessoa pode ter vários crushs.

Ei, mas não temos que mudar o Model de Crushs? Nesse caso não! Pois, em um relacionamento de 1:N vários Crushs ainda pertencem a Uma pessoa o que justifica o uso do belongsTo.

5 - Relacionamento de N:N (Eu + os contatinhos 😍😍😍 depois da terapia 😌)

Esse é o momento onde as coisas ficam um pouco mais complicadas mais, calma! Liga aquela playlist de Tim Maia Lofi que você já conhece!

Nosso cliente assim como Raul Seixas, deu um migué, cantou: ‘Amor só dura em liberdade, o ciúme é só vaidade, sofro, mas eu vou te libertar!'

E agora os contatinhos também podem ter contatinhos!

Então nossa tabela Persons, pode possuir vários Crushs E Crushs podem pertencer a várias Persons.

Como a gente faz isso, na prática? Bora!

Agora temos que ter mais uma tabela de ligação para fazer esse relacionamento, na prática, um relacionamento de N:N são dois relacionamentos de 1:N em uma tabela de ligação.

Como você pode ver na imagem:

Agora temos 3 tabelas, Persons, Persons_crushers, Crushers

A tabela Persons guardara as informações de cada Pessoa A tabela Crushers guardara as informações de cada Crusher E a tabela Persons_crushers guardara os relacionamentos entre essas duas tabelas onde uma pessoa poderá ter vários crushes e um crush poderá pertencer a várias pessoas, sendo assim podemos chamar Person_crushers de Tabela de Junção!

Agora vamos para prática (e alterar o destino da felicidade de várias pessoas 😄)!

Primeiro vamos alterar o model de Person:

module.exports = (sequelize, DataTypes) => {
  const Person = sequelize.define('Person', {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    name: DataTypes.STRING,
    surname: DataTypes.STRING,
  },
  {
    timestamps: false,
    tableName: 'Persons',
  });

  return Person;
};

Depois o model de Crush:

module.exports = (sequelize, DataTypes) => {
  const Crush = sequelize.define('Crush', {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    name: DataTypes.STRING,
    surname: DataTypes.STRING,
    crusher_id: {type: DataTypes.INTEGER, foreignKey: true}
  },
  {
    timestamps: false,
    tableName: 'Crushs',
  });

  return Crush;
};

Até agora nada de novo, nós alteramos as tabelas logo tiramos a função associate. A grande diferença virá aqui!

Vamos criar o model de PersonCrushs:

module.exports = (sequelize, _DataTypes) => {
  const PersonCrush = sequelize.define('PersonCrush',
    {},
    { timestamps: false },
  );

  PersonCrush.associate = (models) => {
    models.Crush.belongsToMany(models.Person, {
      as: 'persons',
      through: PersonCrush,
      foreignKey: 'crush_id',
      otherKey: 'person_id',
    });
    models.Person.belongsToMany(models.Crush, {
      as: 'crushers',
      through: PersonCrush,
      foreignKey: 'person_id',
      otherKey: 'crusher_id',
    });
  };

  return PersonCrush;
};

E aqui vemos a mágica do Sequelize acontecendo, não precisamos passar nenhum atributo para o modelo de PersonCrush justamente porque estamos informando que PersonCrush é uma tabela de ligação, na chave through estamos informando qual o Model que servira como tabela de associação. Chamamos 'as:' como nome dessa associação e por último os campos que dizem ao Sequelize como faze la foreignKey e a otherKey.

Agora para testarmos nossa aplicação, vamos ao Node!

const { Crush, Person } = require('./models');
// ...
app.get('/personcrusher/:id', async (req, res) => {
  try {
    const { id } = req.params;
    const person = await Person.findOne({
      where: { personId: id },
      include: [{ model: Crush, as: 'crush', through: { attributes: [] } }],
    });

    if (!person)
      return res.status(404).json({ message: 'Usuário não encontrado' });

    return res.status(200).json(person);
  } catch (e) {
    console.log(e.message);
    res.status(500).json({ message: 'Algo deu errado' });
  };
});

Para finalizar faça uma requisição do tipo GET para este endpoint passando como parâmetro o ID de um usuário existente.

Faça um teste tirando a opção attributes e veja a diferença!

Conclusão

Assim conseguimos de forma rápida fazer associação entre tabelas o que daria muito trabalho se tivéssemos que fazer na mão. A documentação do Sequelize trata as associações de forma detalhada e é referência obrigatória na hora de criar sua aplicação, no próximo artigo falarei um pouco sobre Eager Loading e Lazy Loading, até lá!