Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso ORM com Node.js: avançando nas funcionalidades do Sequelize

ORM com Node.js: avançando nas funcionalidades do Sequelize

Soft delete - Apresentação

Olá, boas-vindas ao curso de API REST usando Node.js, Express e Sequelize, onde vamos avançar nas funcionalidades do Sequelize.

Audiodescrição: Juliana Amoasei é uma mulher branca, de olhos castanhos e cabelos curtos, lisos e pintados de azul. Usa óculos com moldura fina e arredondada, na cor vermelha. Possui brincos e piercings nas orelhas e nariz. Está em um dos estúdios da Alura.

Para quem é este curso?

Este conteúdo é para você que está iniciando nos estudos de back-end com Node.js. Ou seja, já praticou criando suas primeiras APIs nos cursos anteriores da formação e deseja aprender novas ferramentas e utilizar novos tipos de banco e o que eles têm para oferecer para nós.

Este curso é a continuação imediata do curso anterior de Sequelize, que está nos pré-requisitos deste curso. No curso anterior, nós montamos a estrutura da API e já conhecemos a parte inicial da ferramenta do Sequelize, por exemplo, modelos, associações e migrações. Então, é importante que você tenha feito o curso anterior.

Você vai aprender:

Neste curso, você adquirirá conhecimentos sobre as funcionalidades avançadas do ORM que estamos atualmente usando, o Sequelize, e como este ORM se integra com um banco de dados em relação a uma API REST desenvolvida utilizando Node.js.

Nós vamos acrescentar mais funcionalidades seguindo uma lista de requisitos que recebemos para implementar a nossa API do nosso cliente. Vamos aprender também como fazer consultas, queries mais específicas ao banco, utilizando métodos do Sequelize.

Vamos entender mais conceitos e mais ferramentas que são do SQL e como o ORM, o Sequelize, utiliza essas ferramentas para acessar o SQL e como ele transfere essas funcionalidades do SQL para os métodos do Sequelize.

Nós também vamos praticar um pouco mais com algumas das camadas, das partes principais da nossa API: rotas, models, controllers. Vamos refatorar, reaproveitar código, adicionar mais serviços e deixar o código também bem organizado.

Pré-requisitos para realizar este curso

É fundamental que tenha concluído o curso anterior de Sequelize, no qual montamos a API e realizamos nossas primeiras práticas com a ferramenta.

No entanto, se você já possui experiência em trabalhar com APIs usando Sequelize e está familiarizado com conceitos básicos, como modelos, associações entre modelos, etc., você pode prosseguir com este curso. No entanto, recomendamos enfaticamente que tenha concluído o curso anterior para obter o máximo proveito desta experiência.

Aproveite os recursos da plataforma; temos, além dos vídeos, muitas atividades, o apoio do fórum e a comunidade no Discord.

Vamos começar a codificar!

Soft delete - Revisando o projeto

Continuaremos do ponto onde paramos no curso anterior de Sequelize. Porém, caso queira recomeçar com um repositório igual ao que eu vou utilizar no curso (comecei um repositório novo para este curso), deixaremos o link para você realizar o download.

Caso queira continuar com o repositório do curso anterior, também pode. Porém, do curso anterior para esse, adicionamos algumas pessoas, alguns cursos nas tabelas do nosso banco database.sqlite. Se quiser substituir o banco que possui do curso anterior por este, para nós termos uma paridade de registros que vão retornar ao nosso JSON, você pode fazer isso também.

Refatorando a estrutura de pastas

Antes de continuarmos, faremos uma pequena refatoração para dar uma organizada na nossa estrutura de pastas, porque o nosso diretório src, ele está com muita coisa dentro, está meio bagunçado.

Dentro de src, criamos uma nova pasta chamada de database. Para isso, clicamos no segundo ícone abaixo de "Explorer" na parte superior.

O que vamos colocar dentro dessa pasta database? Lembrando do que conversamos no curso anterior sobre camadas da API. Temos a camada de modelos, temos a camada de controladores, e temos a camada do banco de dados, da nossa database.

Dentro de src, o que temos que faz referência à database? Nós temos a pasta config, nós temos a pasta migrations, models, o mais importante, e seeders. Selecionamos, clicando com o "control" esses quatro diretórios. Iremos arrastá-los para dentro de database. O VSC vai pedir para confirmar, selecionamos "Ok" e confirmamos.

Além disso, dentro de database, criamos mais uma pasta manualmente chamada de storage. Arrastamos arquivo database.sqlite para dentro de storage, porque ele tinha ficado meio perdido na raiz do projeto. E ele, o nosso banco de dados, também faz parte da camada de database, da camada de dados.

Agora, se observarmos as pastas que estão dentro de src, elas estão refletindo melhor, a nossa organização de pastas reflete melhor as camadas do nosso projeto, as camadas da nossa API.

Então, controllers, camada de dados, a database, routes e services.

Na parte externa, temos os pontos de entrada, tanto do servidor quanto do nosso app.js, que é o que inicia o Express. Só que agora, como modificamos locais, modificamos caminhos, precisamos** organizar novamente para a API não se perder**.

Organizando os caminhos

Onde temos caminhos para arrumar dentro da API? Dentro de "database > config" temos o arquivo config.json, que é onde passamos o caminho da database em si, o caminho do nosso arquivo.

config.json

{
"development": {
"dialect": "sqlite",
"storage": "./database.sqlite"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}

Como que agora passamos dentro da propriedade storage? Estávamos passando /database.sqlite porque ele estava na raiz. Agora, o que podemos fazer é ir dentro de storage, clicar com o botão direito em database.sqlite e selecionar a opção "Copy Relative Path" (Copiar o Caminho Relativo) ou podemos usar o atalho "Alt + Ctrl + Shift + C".

Copiamos o caminho e dentro do arquivo config.json colamos, ele vai exibir exatamente src/database/storage/database.sqlite, que é agora o caminho do nosso arquivo de banco de dados com relação ao ponto de entrada da API.

Na propriedade storage, substituímos ./database.sqlite por ./src/database/storage/database.sqlite.

config.json

{
"development": {
"dialect": "sqlite",
"storage": "./src/database/storage/database.sqlite"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}

É muito importante verificar se o caminho para o arquivo SQLite está correto, porque o comportamento padrão do SQLite é criar um novo arquivo se ele não encontrar o existente, e isso pode causar muita confusão.

Outro ponto muito importante é onde temos o caminho que precisamos arrumar? É no arquivo .sequelizerc, que está na raiz do projeto.

.sequelizerc

const path = require('path');

module.exports = {
'config': path.resolve('./src/config', 'config.json'),
'models-path': path.resolve('./src/models'),
'seeders-path': path.resolve('./src/seeders'),
'migrations-path': path.resolve('./src/migrations'),
}

Este arquivo literalmente resolve os caminhos quando usamos a ferramenta de CLI do Sequelize, o Sequelize-CLI, para que ele consiga encontrar onde estão os arquivos que ele precisa trabalhar.

Agora, onde está src/config, src/models, src/seeders, etc., temos que adicionar a pasta database, para onde mandamos essas coisas.

Selecionamos "src/", na primeira ocorrência dele, usamos o "Ctrl + D" para selecionar todas as ocorrências iguais e incluir "database/" no final.

.sequelizerc

const path = require('path');

module.exports = {
'config': path.resolve('./src/database/config', 'config.json'),
'models-path': path.resolve('./src/database/models'),
'seeders-path': path.resolve('./src/database/seeders'),
'migrations-path': path.resolve('./src/database/migrations'),
}

Agora, tudo está consertado: src/database/config, src/database/models, etc. Vamos salvar o arquivo. O último local onde precisamos arrumar os caminhos é justamente qual é o ponto de contato da nossa API com a parte do banco.

Está na pasta services. Lembra que no curso passado nós organizamos a nossa API e desacoplamos os controllers dos models, ou seja, todo o contato com o banco de dados está sendo feito a partir da camada de services. A camada de services é o único ponto onde temos que refazer esses caminhos.

Dentro de services, no arquivo services.js, lá no topo do arquivo, na primeira linha, onde importamos os nossos modelos, ao invés de require('../models'), agora é require('../database/models').

services.js

const dataSource = require('../database/models');

// código omitido

Como os nossos serviços específicos não estão importando diretamente dos models, eles importam apenas de services.js mesmo, não temos mais nenhum outro local, nenhum outro ponto de contato da database com o resto da aplicação.

Isso é bom para quando temos que fazer esse tipo de refatoração, fica mais fácil organizar onde temos que mexer.

Aproveitando que estamos falando de refatoração, lá dentro de controllers, em controller.js, no curso anterior faltou trabalharmos com uma parte de tratamento de erros. Todos os nossos catches, eles estão por enquanto com um comentário // erro.

Vamos alterar isso para podermos ter um controle literalmente melhor do que acontece quando dá erro na nossa aplicação?

Em todos os lugares, da mesma forma que fizemos anteriormente, vamos selecionar o "// erro" e teclamos "Ctrl + D" para localizar todos os pontos onde tem esse comentário.

Substituímos esse comentário por return res.status(500).json({ erro: erro.message }). Aqui, erro está em português porque é um objeto que será recebido dentro do catch se houver algum problema. O erro.message tem que estar em inglês mesmo porque é uma propriedade do objeto Error no JavaScript.

Todos os objetos de error possuem a propriedade message, sendo uma mensagem por extenso, e é justamente isso que desejamos inserir no JSON para resolver os problemas que ocorrerão no desenvolvimento.

controller.js

// código omitido

} catch (erro) {
return res.status(500).json({ erro: erro.message });
}
}

// código omitido

No arquivo controller.js o VSC está acusando um erro, e esse erro está no método exclui, porque fiz um erro de digitação e no catch, ao invés de catch (erro) em português, ficou error em inglês com r no final. Assim, ficamos com catch(erro). Vou corrigir isso.

Podemos também verificar se existe alguma mensagem de erro que precisa ser corrigida dando um "Ctrl + Shift + F" e procurando por // erro. Aí tem só mais um lugar dentro de PessoaController onde precisamos fazer essa substituição. Copiamos a linha que acabamos de criar, do return res.status(500).json({ erro: erro.message }), e em PessoaController e substituímos pelo único lugar onde faltou.

PessoaController

// código omitido

} catch (erro) {
return res.status (500).json({ erro: erro.message });

// código omitido

Agora, se temos erros durante o nosso desenvolvimento e eles são pegos no controller, então, basicamente, erros de resposta da requisição, conseguimos capturá-los, ele vai jogar para nós o JSON e o capturamos, por exemplo, no Postman ou no serviço que estivermos utilizando.

Agora, podemos iniciar o servidor. No terminal, na pasta do projeto do curso, onde já instalamos o projeto com npm install, basta executar npm run dev para verificar se tudo está correto.

npm run dev

Obtemos:

servidor escutando!

Subiu, o Nodemon informou que o servidor escutando.

No Postman, vamos só testar as rotas que deixamos criadas anteriormente. Faremos um GET em localhost:3000/categorias, clicando no botão "Send" do lado direito. Observem na parte inferior que continua chegando, temos o id 1 com o título "Node.js", o campo informando a criação e a atualização.

Em /pessoas, vamos testar também, continua vindo. Tem até os registros a mais que criei. E /cursos também está vindo. Lembrando que matrícula é um pouco diferente, vamos conversar mais sobre matrícula durante este curso, mas, por enquanto, está tudo funcionando.

Conclusão e Próximos Passos

Já organizamos as camadas da nossa API, agora elas refletem um pouco melhor a nossa organização de pastas. Já arrumamos um pouco, fizemos um tratamento de erro bem incipiente, mas que já vai nos ajudar se tivermos problemas no desenvolvimento.

Agora, podemos continuar desenvolvendo a nossa API, acrescentando funcionalidades nela.

Até a próxima aula!

Soft delete - Ocultar sem deletar

Projeto organizado, vamos continuar alterando o nosso projeto.

Mas o que precisamos fazer, afinal? Dentro da pasta arquivos-base do lado superior esquerdo, deixamos pronto um arquivo que se chama requisitos.md, em que "md" significa markdown.

Vamos abrir um preview (pré-visualização) dele para conferirmos. Para isso, clicamos com o botão direito sobre "requisitos.md" e optamos por "Open Preview".

Disponibilizamos a primeira versão de nossa API como parte de um produto inicial. Subsequentemente, nossa equipe de produto se envolveu em discussões com o cliente, resultando em uma lista de requisitos e funcionalidades que devem ser incorporados à nossa API.

Conversamos, e conseguimos priorizar o que o cliente achava que era mais importante adicionarmos como funcionalidade, e é o que vamos fazer nesse projeto, usando agora os Sequelize, uma vez que a nossa API já está montada e organizada, já vimos os modelos, fizemos associações, etc.

A partir de agora, começamos a seguir essa lista de requisitos. Vamos analisar o que tem nos requisitos do projeto para começarmos a implementar um por um.

Analisando os requisitos

requisitos.md

+ O cliente não gostaria que registros importantes do sistema, como as Pessoas, sejam apagados definitivamente do banco de dados.

+ Para deixar a interface mais limpa, o cliente gostaria que na lista de Pessoas, por padrão, fossem exibidos somente os usuários ativos.

+ Foram percebidas algumas falhas de validação dos formulários por parte do front-end, o que resultou em dados de email inválidos no banco. É desejável que essa validação não seja responsabilidade exclusiva do front.

+ É importante poder consultar todas as matrículas confirmadas referentes a estudante X de forma rápida.

+ O cliente gostaria de poder consultar as turmas abertas por intervalo de data, para não receber informações desnecessárias (como turmas antigas).

O cliente quer poder consultar as matrículas por turma e saber quais delas estão lotadas, para organizar melhor as matrículas.

+ O cliente gostaria que, uma vez que o cadastro de um estudante fosse desativado, todas as matrículas relativas a este estudante automaticamente passassem a constar como “canceladas”.

O primeiro requisito é: o cliente não gostaria que registros importantes do sistema, como as pessoas, sejam apagados definitivamente do banco de dados. Sempre mencionamos que operações de banco são operações definitivas.

Portanto, se mandamos apagar algo do banco, como uma tabela, um "DROP TABLE", isso é definitivo. Como apagamos sem apagar? Como fingimos que estamos apagando, digamos assim?

Para entendermos o que precisa ser feito, vamos analisar como estão as colunas do nosso banco de dados. Entramos em em "database > storage > database.sqlite". Ao clicar em "database.sqlite", o software abrirá automaticamente todas as tabelas desse banco de dados. Isso ocorre porque estou utilizando uma extensão do VSCode que difere daquela que utilizei no curso anterior para acessar as tabelas do banco, chamada "SQLite Viewer".

Se você deseja continuar usando a extensão que foi utilizada no curso passado, pode, sem problema, que é somente SQLite. No entanto, a SQLite Viewer é um pouco mais fácil de visualizarmos o banco, então eu vou utilizar essa neste curso. Vou deixar um link para você baixar se quiser.

Do lado esquerdo, temos 6 tabelas:

Clicamos em database.sqlite, ele já mostra uma lista das tabelas do banco. E se acessarmos, por exemplo, "pessoas" e consultarmos as tabelas, temos duas colunas, que são CreatedAt e UpdatedAt.

O que precisamos fazer é marcar no banco, porque lembre-se que são os registros que têm que ser apagados de uma forma não definitiva. Portanto, temos que dar um jeito de avisar isso para o banco, de fazer uma marcação no banco de dados dizendo, essa linha aqui, por exemplo, a linha que tem o ID 1, vamos apagar ela de forma não definitiva.

Como fazemos isso, então?

Removendo do banco de dados de forma não definitiva

Esse processo é o que chamamos de Soft Delete (exclusão suave) e ele é uma estratégia muito comum de dados. Se é muito comum, provavelmente o SQLite tem uma forma de trabalhar com ela, e no SQLite chamamos isso de Paranoid.

O nome da propriedade nesse ORM é Paranoid .

Vamos entender como marcamos no modelo para isso se refletir no banco e informar que quando excluirmos qualquer coisa deste do modelo "pessoas", vai se refletir na tabela pessoas como uma exclusão suave. E agora temos que adicionar o código dizendo isso nos modelos, que é o que está sendo informado na documentação.

Fechamos a parte de database e seguimos para "database > models". No modelo pessoa.js , onde precisamos inserir essa informação de Paranoid? Isso deve ser feito dentro de Pessoa.init.

Portanto, no segundo objeto, que é o segundo parâmetro de Pessoa.init, sendo que o primeiro parâmetro é um objeto com as propriedades e campos, enquanto o segundo é um objeto de opções, onde podemos configurar detalhes adicionais. Após modelName e tableName, incluiremos a opção Paranoid e a configuraremos como true.

pessoa.js

// código omitido

  }, {
    sequelize,
    modelName: 'Pessoa',
    tableName: 'pessoas',
    paranoid: true,
  });
  return Pessoa;
};

No modelo, é só isso que temos que fazer. Como todos esses quatro modelos, pessoas, categorias, estão bastante relacionados, vamos passar o Paranoid: true para todos eles, para visualizarmos isso em ação. Dentro do modelo matricula.js , adicionamos também no mesmo local, Paranoid: true.

matricula.js

// código omitido

 }, {
    sequelize,
    modelName: 'Matricula',
    tableName: 'matriculas',
    paranoid: true,
  });
  return Matricula;
};

Em curso.js também, a mesma coisa. Adicionamos o Paranoid: true e em categoria.js , a mesma coisa.

curso.js

// código omitido

  }, {
    sequelize,
    modelName: 'Categoria',
    tableName: 'categorias',
        paranoid: true,
  });
  return Categoria;
};

Isso significa apenas que o modelo está avisado, que agora vamos usar a estratégia de soft delete nesses modelos. Só que o banco também precisa ser avisado. Como é que fazemos, então, a parte do banco de dados? Porque não temos nenhuma coluna no banco de dados que reflita nessa alteração. Só temos as colunas DeletedAt e UpdatedAt.

Ou seja, precisamos adicionar uma coluna em todas as tabelas. Nós já sabemos como criar tabelas usando migrações, então metade do caminho está andado. Vamos adicionar essas colunas e finalizar essa parte de soft delete, deixando aí nossos modelos paranoicos.

Vamos lá!

Sobre o curso ORM com Node.js: avançando nas funcionalidades do Sequelize

O curso ORM com Node.js: avançando nas funcionalidades do Sequelize possui 178 minutos de vídeos, em um total de 57 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas