npm init -y
npm install -E sequelize@6.3.4
npm install -D -E sequelize-cli@6.2.0
npm install -E mysql2@2.1.0
npm i -E express@4.17.1 nodemon@2.0.15
- Criar o arquivo
.env
:
MYSQL_USER=root
MYSQL_PASSWORD=senha_mysql
MYSQL_DATABASE=nome_do_banco
MYSQL_HOST=localhost
- Criar o arquivo
.sequelizerc
com as configurações:
const path = require('path');
module.exports = {
'config': path.resolve('src', 'config', 'config.js'),
'models-path': path.resolve('src', 'models'),
'seeders-path': path.resolve('src', 'seeders'),
'migrations-path': path.resolve('src', 'migrations'),
};
npx sequelize-cli init
: Inicialização do Sequelize- Alterar o arquivo
config.js
const config = {
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
host: process.env.MYSQL_HOST,
dialect: 'mysql',
port: 3306 // padrão
};
module.exports = {
development: {
...config,
database: 'database_dev',
},
test: {
...config,
database: 'database_test',
},
production: {
...config,
database: 'database_prod',
},
};
- Configurar o
docker-compose.yml
:
version: '3'
services:
database:
image: mysql:8.0.29
container_name: nome-do-container
environment:
MYSQL_ROOT_PASSWORD: senha_mysql
ports:
- "3306:3306"
- Subir o container com
docker-compose up -d
env $(cat .env) npx sequelize db:create
: criar o banco de dadosnpx sequelize migration:generate --name nome-da-migration
: criar o arquivo esqueleto da migrationenv $(cat .env) npx sequelize db:migrate
: criar tabelaenv $(cat .env) npx sequelize db:migrate:undo
: reverternpx sequelize seed:generate --name nome-do-seed
: criar o esqueleto de uma nova seedenv $(cat .env) npx sequelize db:seed:all
: executa todos os arquivos de seed que ainda não foram executadosenv $(cat .env) npx sequelize db:seed --seed nome-do-seed.js
: executa o arquivo de seed com o nome especificadoenv $(cat .env) npx sequelize db:seed:undo:all
: desfaz todas as seeds que foram executadas- No arquivo
package.json
"dev": "nodemon src/server.js"
env $(cat .env) npm run dev
: iniciar o servidor
Migrations: Criação do banco de dados (tabelas, colunas)
Seeders: Inserção de dados iniciais
Models: Leitura e escrita dos dados (insert, update, delete, select)
Para criar tabela:
module.exports = {
up: async (queryInterface, Sequelize) => {
return queryInterface.createTable('addresses', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
city: {
allowNull: false,
type: Sequelize.STRING,
},
street: {
allowNull: false,
type: Sequelize.STRING,
},
number: {
allowNull: false,
type: Sequelize.INTEGER,
defaultValue: 0 // é possível colocar um valor default
},
employeeId: {
type: Sequelize.INTEGER,
allowNull: false,
// Configuram o que deve acontecer ao atualizar ou excluir um usuário
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
field: 'employee_id',
references: { // Informa que o campo é uma Foreign Key (Chave estrangeira)
model: 'employees', // Informa a tabela da referência da associação
key: 'id', // Informa a coluna da referência que é a chave correspondente
},
},
});
},
down: async (queryInterface, _Sequelize) => {
return queryInterface.dropTable('addresses');
},
};
Para adicionar ou remover uma coluna:
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('Users', 'phoneNum', {
type: Sequelize.STRING,
after: 'email',
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('Users', 'phoneNum');
},
};
module.exports = {
up: async (queryInterface, Sequelize) => queryInterface.bulkInsert('Users',
[
{
fullName: 'Leonardo',
email: 'leo@test.com',
createdAt: Sequelize.literal('CURRENT_TIMESTAMP'),
updatedAt: Sequelize.literal('CURRENT_TIMESTAMP'),
},
{
fullName: 'JEduardo',
email: 'edu@test.com',
createdAt: Sequelize.literal('CURRENT_TIMESTAMP'),
updatedAt: Sequelize.literal('CURRENT_TIMESTAMP'),
},
], {}),
down: async (queryInterface) => queryInterface.bulkDelete('Users', null, {}),
};
*Obs: No Seeder, não é necessário passar o id (PK).
- Os métodos de criação de associações que o Sequelize disponibiliza são:
- A.hasOne(B) e B.belongsTo(A): relacionamentos 1:1
- A.hasMany(B) e B.belongsTo(A): relacionamentos 1:N
- A.belongsToMany(B) e B.belongsToMany(A): relacionamentos N:N
Exemplo: Relacionamento 1:N
module.exports = (sequelize, DataTypes) => {
const Address = sequelize.define('Address', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
city: DataTypes.STRING,
street: DataTypes.STRING,
number: DataTypes.INTEGER,
employeeId: { type: DataTypes.INTEGER, foreignKey: true }, // A declaração da Foreign Key é opcional no model
},
{
timestamps: false, // remove a obrigatoriedade de utilizar os campos `createdAt` e `updatedAt`
tableName: 'addresses',
underscored: true, // automaticamente o model converte o camelCase do js para snake_case do banco de dados
});
Address.associate = (models) => {
Address.belongsTo(models.Employee,
{ foreignKey: 'employeeId', as: 'employees' });
};
/*
No model Employee:
Employee.associate = (models) => {
Employee.hasMany(models.Address,
{ foreignKey: 'employeeId', as: 'addresses' });
};
*/
return Address;
};
Exemplo: Relacionamentos N:N - tabela intermediária de junção
module.exports = (sequelize, DataTypes) => {
const PatientSurgery = sequelize.define('PatientSurgery',
{
patientId: {
type: DataTypes.INTEGER,
primaryKey: true,
references: {
model: 'Patients', // nome da tabela à qual a chave estrangeira se refere
key: 'patientId', // nome da coluna na tabela referenciada que é a chave primária
}
},
surgeryId: {
type: DataTypes.INTEGER,
primaryKey: true,
references: {
model: 'Surgeries',
key: 'surgeryId',
}
}
},
{
timestamps: false,
underscored: true,
tableName: 'Patient_surgeries',
}
);
PatientSurgery.associate = (models) => {
models.Surgery.belongsToMany(models.Patient, {
as: 'patients', // Nome da associação no model
through: PatientSurgery, // Tabela de junção
foreignKey: 'surgeryId', // Chave estrangeira na tabela de junção que referencia Surgery
otherKey: 'patientId', // Chave estrangeira na tabela de junção que referencia Patient
});
models.Patient.belongsToMany(models.Surgery, {
as: 'surgeries',
through: PatientSurgery,
foreignKey: 'patientId',
otherKey: 'surgeryId',
});
};
return PatientSurgery;
};
- Importar o model no arquivo service. Exemplo:
// arquivo service
const { User } = require('../models');
- findAll: buscar todas as linhas da tabela. Equivale a fazer a query: SELECT * FROM Users
const getAll = async () => {
const users = await User.findAll();
return users;
};
- Com where:
const getCommentsByAccountId = async (accountId) => {
const comments = await Comment.findAll({
where: { accountId },
attributes: { exclude: ['accountId'] },
});
return comments;
};
Obs: exclude retira a coluna desejada do retorno
- Com relacionamento com outra tabela:
const getAll = async () => {
const students = await Student.findAll({
include: { model: Course, as: 'course', attributes: ['name', 'id'] },
});
return users;
};
Obs: attributes serve para trazer somente as colunas desejadas
- Procurar por algum atributo específico: utilizar o where
const getByAuthor = async (author) => {
const books = await Book.findAll({
where: { author },
order: [['title', 'ASC']],
});
return books;
};
Obs: order serve para trazer os resultados ordenados
- findByPk: Buscar o objeto por id. Equivale a fazer a query: SELECT * FROM Users WHERE id=?
const getById = async (id) => {
const user = await User.findByPk(id);
return user;
};
- findOne: Combinado com a chave where para buscar por id e por outro valor. Equivale a fazer a query: SELECT * FROM Users WHERE id=? AND email=?
const getByIdAndEmail = async (id, email) => {
const user = await User.findOne({ where: { id, email } });
return user;
};
- create: inserir um objeto na tabela. Equivale a fazer a query: INSERT INTO Users (full_name, email) VALUES (?, ?)
const createUser = async (fullName, email) => {
const newUser = await User.create({ fullName, email });
return newUser;
};
- update: atualizar um objeto na tabela. Equivale a fazer a query: UPDATE Users SET full_name=?, email=? WHERE id=?*/
const updateUser = async (id, fullName, email) => {
const [updatedUser] = await User.update(
{ fullName, email },
{ where: { id } },
);
return updatedUser; // é retornado 0 ou 1
};
- destroy: remover um objeto na tabela. Equivale a fazer a query: DELETE FROM Users WHERE id=?*/
const deleteUser = async (id) => {
const user = await User.destroy(
{ where: { id } },
);
return user; // é retornado 0 ou 1
};
- Unmanaged transactions: Transação não gerenciada. É preciso indicar manualmente a circunstância em que uma transação deve ser finalizada ou revertida (executar o commit ou rollback).
const insert = async ({ firstName, lastName, age, city, street, number }) => {
// Jeito antigo - sem transações
// const employee = await Employee.create({ firstName, lastName, age });
// await Address.create({ city, street, number, employeeId: employee.id });
// return employee;
// Transação - respeitando a atomicidade
const t = await sequelize.transaction();
try {
const employee = await Employee.create({ firstName, lastName, age }, { transaction: t });
await Address.create({ city, street, number, employeeId: employee.id }, { transaction: t });
await t.commit(); // finalizar a transação usando a função `commit`.
return employee;
} catch (e) {
await t.rollback(); // reverter as operações anteriores com a função rollback
console.log(e);
throw e;
}
};
- Managed transactions: Transação gerenciada. O próprio Sequelize fica responsável por realizar o gerenciamento das transações e determina, em tempo de execução, quando deve finalizar ou reverter uma transação.
const insert = async ({ firstName, lastName, age, city, street, number }) => {
const result = await sequelize.transaction(async (t) => {
const employee = await Employee.create({ firstName, lastName, age }, { transaction: t });
await Address.create({ city, street, number, employeeId: employee.id }, { transaction: t });
return employee;
});
return result;
};
- Instalar as dependências:
npm i mocha@10.0.0 chai@4.3.4 sinon@14.0.0 chai-http@4.3.0 sequelize-test-helpers@1.4.3 -D -E
- Criar o script de testes no arquivo
package.json
:
"test": "mocha tests/**/*$NAME*.test.js --exit"
- Importar os helpers:
const { sequelize, dataTypes, checkModelName, checkPropertyExists } = require('sequelize-test-helpers');
- Exemplo de testes para a Model:
const BookModel = require('../../src/models/book.model');
describe('O model de Book', () => {
const Book = BookModel(sequelize, dataTypes);
const book = new Book();
describe('possui o nome "Book"', () => {
checkModelName(Book)('Book');
});
describe('possui as propriedades "title", "author", "pageQuantity"', () => {
['title', 'author', 'pageQuantity'].forEach(checkPropertyExists(book));
});
});
- Exemplo de testes para Service
const { expect } = require('chai');
const { stub } = require('sinon');
const { Book } = require('../../src/models');
const BookService = require('../../src/services/book.service');
const testBook = {
id: 1,
title: 'Cem Anos de Solidão',
author: 'Gabriel García Márquez',
pageQuantity: 419,
createdAt: '2023-08-23 01:00:00',
updatedAt: '2023-08-23 01:00:00',
};
describe('BookService', () => {
describe('#getAll', () => {
const findAllStub = stub(Book, 'findAll');
let books;
describe('Não existe nenhum livro cadastrado', () => {
before(async () => {
findAllStub.resolves([]);
books = await BookService.getAll();
});
after(() => {
findAllStub.reset();
});
it('called Book.findAll', () => {
expect(Book.findAll.calledOnce).to.be.equals(true);
});
it('a resposta é um array', () => {
expect(books).to.be.an('array');
});
it('o array está vazio', () => {
expect(books).to.be.empty;
});
});
describe('Existem livros cadastrados', () => {
before(async () => {
findAllStub.resolves([testBook]);
books = await BookService.getAll();
});
after(() => {
findAllStub.restore();
});
it('called Book.findAll', async () => {
expect(Book.findAll.calledOnce).to.be.equals(true);
});
it('a resposta é um array', async () => {
expect(books).to.be.an('array');
});
it('o array deve retornar objetos', async () => {
expect(books).to.be.deep.equal([testBook]);
});
});
});
});
- Arquivo
package.json
:
"scripts": {
"dev": "nodemon src/server.js",
"db:reset": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all"
},
- Arquivo
docker-compose.yml
:
version: '3.1'
services:
node:
build: .
ports:
- 5556:3000
environment:
MYSQL_USERNAME: root
MYSQL_PASSWORD: senha
MYSQL_DATABASE: nome-do-banco
MYSQL_HOST: db # serviço do container
MYSQL_PORT: 3306 # porta do container
volumes:
- ./src:/app/src
db:
image: mysql:8.0.23
ports:
- 5555:3306
environment:
MYSQL_ROOT_PASSWORD: senha
- Dockerfile do backend:
FROM node:18-alpine
WORKDIR /app
COPY package* ./
RUN npm clean-install
COPY . .
CMD ["npm", "run", "dev"]
- Arquivo config
const config = {
username: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD || 'senha',
host: process.env.MYSQL_HOST || '127.0.0.1', // === localhost
port: process.env.MYSQL_PORT || 5555, // padrão é 3306
dialect: 'mysql',
};
module.exports = {
development: {
...config,
database: process.env.MYSQL_DATABASE || 'nome-do-banco_dev',
},
test: {
...config,
database: 'nome-do-banco_test',
},
production: {
...config,
database: 'nome-do-banco',
},
};
- Iniciando a aplicação no Docker Compose:
docker-compose up -d
docker-compose exec node sh
npm run db:reset
Todos os direitos reservados à Trybe e à documentação oficial do Sequelize (https://sequelize.org/docs/v6/getting-started/), cujos materiais serviram de base para a construção deste gist.