Estrutura MVC com NodeJS
Nesta lição, você vai aprender a utilizar um dos padrões arquiteturais frequentemente usados no desenvolvimento de aplicações web, o famoso MVC.
Também conhecido como padrão Modal-View-Controller, ele costuma ser o preferido, pela sua simplicidade e fácil aplicabilidade.
Quando falamos em construir aplicações em NodeJS, falamos também na melhor forma de manter nossas aplicações seguras, rápidas e ORGANIZADAS!
O conteúdo que veremos a seguir, está relacionado com as boas práticas de organização e manutenção de código, que foi e continua sendo discutida pela maioria dos livros, cursos, vídeo-aulas e tutoriais que vemos por aí pela internet.
E na jornada do NodeJS, não poderiamos simplesmente ignorar este assunto, não é verdade? 😉
Vale a pena manter a sua aplicação organizada?
Se você está me acompanhando desde o início dessa jornada, já identificou que a maioria dos "mini-projetos" que fizemos até agora, não seguem nenhum tipo de padrão arquitetural.
No máximo em algumas lições, eu cheguei a separar partes da lógica em pastas diferentes, tais como a pasta db, pasta view e algumas outras...
E sim, eu fiz tudo aquilo de forma proposital 😅
Motivo? Cada uma daquelas lições servem como guias de consulta, então quanto mais simples e direto eu mostrasse a aplicabilidade daquele conteúdo, mais rápido você iria absorver o funcionamento dele.
Ou você prefere ver conteúdos misturados? (Tem algumas pessoas que curtem, ue? rsrs)
A ideia de separar cada lição em seu devido lugar, ensinando-a de forma crua, vai muito na ideia onde cada lição serve como pequenos HOW TOs, que ensinam especificamente como da forma mais direta possível, ou seja, sem depender de outros conceitos.
Imagina que você está construíndo um carro, na maioria das graduações que ensinam a fazer isso, primeiro eles te ensinam a construir a planta daquele carro, em seguida o motor, as rodas, carroceria, tecnologia e afins...
O problema é que a maioria desses cursos, te ensinam a fazer isso de forma agrupada, ou seja, não existe um conteúdo especifico que SÓ te ensine a construir uma roda, ou somente o motor, ou somente a carroceria... não, tais ensinamentos estão juntos e misturados com outros 😢
E se por acaso você acabar se esquecendo sobre "como se constroe uma roda", você se verá obrigado a entender diversos assuntos irrelevantes até chegar no seu ponto de interesse.
Aqui na Micilini, nós fazemos o contrário, separamos cada um dos conteúdos em pequenas partes e focamos neles 🤓
Dito isso, vamos responder a pergunta principal do nosso tópico: "Vale a pena manter a sua aplicação organizada?".
A resposta é SIM!
Manter essa separação clara permite que cada parte do sistema evolua de forma independente, reduzindo a complexidade e facilitando a localização de problemas ou a adição de novas funcionalidades.
O que se torna ideal para equipes que trabalham em conjunto na criação de funcionalidades e manutenção de um mesmo sistema.
Em sistemas onde essa organização é negligenciada, as mudanças tendem a ser mais difíceis de implementar, o código fica mais confuso e a aplicação se torna propensa a bugs.
E é por isso que padrões como MVC (Modal-View-Controller), MVVM (Model-View-ViewModel), MVP (Model-View-Presenter) e tantos outros, se tornam essênciais às nossas aplicações.
Dito isso, vamos entrar um pouco mais a fundo sobre o que é MVC, e como ele pode nos ajudar 🙃
Proposto por Robert C. Martin, esse padrão foca na separação de preocupações e na independência das camadas. A ideia é que as regras de negócio não dependam de detalhes de implementação, como frameworks ou bibliotecas específicas.
O que é MVC? (Modal-View-Controller)
O MVC (Model-View-Controller) é um padrão de arquitetura de software que separa uma aplicação em três componentes principais (Modal, View e Controller), cada um com responsabilidades distintas.
Cada um dos três componentes, facilita a manutenção, escalabilidade e colaboração em equipe. Veremos o funcionamento de cada um deles abaixo 😉
O Model representa os dados e a lógica de negócios da aplicação. Ele é responsável por acessar e manipular os dados, que podem vir de uma base de dados ou de outros serviços.
A View é responsável pela apresentação dos dados ao usuário. Ela exibe as informações recebidas do Model de maneira visual e interativa.
Além disso, ela é composta por elementos de interface do usuário, como botões, formulários e gráficos, e não deve conter a lógica de negócios.
O Controller atua como um intermediário entre o Model e a View. Ele recebe as entradas do usuário (como cliques de botões ou preenchimento de formulários), processa essas ações (geralmente invocando métodos no Model ou chamando Services) e atualizando a View de acordo.
Olhando assim dessa forma, dá a impressão de que precisamos instalar diversas bibliotecas para que tudo esteja funcionando corretamente, não é verdade?
Mas fique tranquilo que cada um daqueles componentes, nada mais são do que apenas pastas (isso mesmo que você leu... PASTAS) que agrupam classes/funções com funciondalidades específicas.
Então dentro da pasta raiz do seu projeto, você teria uma pasta chamada de Model, que agrupa os arquivos de conexão com o banco de dados e outras operações.
Outra pasta chamada de view, responsável por agrupar as telas da sua aplicação.
E uma outra pasta chamada de controller, que costuma receber as requisições das nossas rotas, além de realizar as devidas operações entre nossas views e models.
No final das contas, o padrão MVC é mais uma maneira que temos para organizar o nosso código, simples assim 🤪
Em softwares mais antigos, nós tinhamos códigos HTML misturados com Java/PHP/C#, junto com consultas em bancos de dados... nossa era uma loucura!
Com a chegada do padrão MVC, tudo foi ficando cada vez mais organizado e simples de se manter... UFA 😅
Observação: muitos frameworks e bibliotecas consolidadas no mercado usam o padrão MVC nativamente durante a instalação das mesmas.
Como funciona o fluxo de interação do padrão MVC?
O fluxo costuma ser tão simples, como o exemplo abaixo:
- O usuário interage com a View, por exemplo, clicando em um botão.
- A View envia a ação do usuário para o Controller.
- O Controller processa essa ação, possivelmente modificando os dados no Model.
- O Model notifica a View sobre as mudanças nos dados.
- A View atualiza a interface do usuário para refletir os dados mais recentes.
Simples, não acha?
Quais os benefícios do MVC?
Quando aplicamos o padrão MVC em nossos projetos, temos uma série de benefícios, tais como:
Separação de Preocupações: o padrão MVC, facilita a manutenção e a testabilidade, pois cada componente pode ser alterado de forma independente.
Reusabilidade: nossos componentes (como Views ou Models) podem ser reutilizados em diferentes partes da aplicação.
Facilidade de Colaboração: quando trabalhamos em um time, as equipes podem trabalhar em diferentes partes da aplicação simultaneamente, sem interferir no trabalho uns dos outros.
E é claro, aplicações que seguem o padrão MVC tendem a ter uma manutenção e evolução de forma mais fácil.
"Tendem", pois se o desenvolvedor começar a bagunçar e não for organizado, nem os padrões arquiteturais poderão salvá-lo 😂
Entendendo a camada de Modelo (Model)
Como você já sabe, é dentro da pasta model que iremos agrupar todos os arquivos responsável por interagir com o nosso banco de dados.
Alí dentro deve existir classes do Javascript que vão trabalhar diretamente com o MongoDB, Prisma, Sequelize, TypeORM... ou seja, classes que executam as operações CRUD
(Create, Read, Update e Delete).
Quando estamos utilizando ORMs, é padrão que cada model seja uma tabela do seu banco de dados relacional.
Posso ter uma view alí dentro, ou qualquer outra lógica que não esteja ligada ao banco de dados?
Poder você até pode, mas aí você estaria quebrando o padrão da sua arquitetura 😤
Além disso, é o model que controla a arquitetura do nosso projeto, o que torna o entendimento do negócio mais fácil.
Entendendo a camada de Visualização (View)
A camada de View
, é representada por uma pasta (com o mesmo nome) que agrupa todos os arquivos .html
, .ejs
, .handlebars
que temos na nossa aplicação.
Ou seja, são os arquivos de UI (User Interface) que serão chamados pelo NodeJS, que por sua vez, já envia os dados que precisam ser mostrados.
Observe a frase: "...já envia os dados que precisam ser mostrados...".
A ideia é que nossas views não contenham (sequer) nenhuma lógica ou regra de negócios dentro dela, e caso isso não for possível, que pelo menos tenhamos o mínimo de lógica possível.
Observação: é comum termos códigos JS que enviam informações de formulários usando FetchAPI dentro da View. Coisas desse tipo são bem vindas, ok?
Entendendo a camada do Controlador (Controller)
Por fim, nós temos a última entidade (não menos importante) do MVC, o controller, que é representado por uma pasta que leva o mesmo nome.
Ele é quem faz o intermédio entre os arquivos da pasta views, e os arquivos da pasta model.
Basicamente, quando o usuário digita alguma URL no navegador, a biblioteca escolhida (imagine a biblioteca do express), é responsável por capturar essa URL, e em seguida, chamar o controller necessário para tratar essa página.
Dentro do controller, ele vai ser responsável por fazer consultas na model (caso necessário), e retornar de volta (ao express) o arquivo que representa uma view (já com os dados retornados pelo model, caso necessário).
Apesar do funcionamento explicado ser aplicado na maioria dos projetos, nem sempre um controller vai retornar uma view, por vezes, ele vai retornar somente os dados retornados por um model.
E os arquivos de conexão com o banco de dados, lógicas que não cabem dentro de um MVC? Rotas?
É verdade que um arquivo de conexão com o banco de dados, um serviço, um utilitário que executa uma operação específica, um arquivo de rota, um middleware, um repositório e etc... não cabem dentro de um model, nem dentro de uma view, e muito menos dentro de um controller, não é verdade?
E o que fazemos com esses arquivos? Se eles não cabem dentro do padrão MVC?
É aí que vem o PULO DO GATO 🐈
O padrão MVC nos instrui a seguir suas regras principais, mas... não nos impede que criemos outras pastas dentro da pasta raiz do nosso projeto 💡
Até porque, algumas lógicas mais complexas ou regras de negócios podem ser extraídas dos controllers e colocadas em serviços (services) separados, o que ajuda a manter os controllers mais limpos, e focados em lidar com a entrada e saída de dados.
No final das contas, nós podemos ter uma estrutura de pastas similar a esta:
/Meu Projeto
├── /controllers
│ ├── userController.js
│ └── productController.js
├── /models
│ ├── userModel.js
│ └── productModel.js
├── /views
│ ├── dashboard.html
│ └── listOfProducts.html
├── /routes
│ ├── userRoutes.js
│ └── productRoutes.js
├── /services
│ ├── userService.js
│ └── productService.js
├── /repositories
│ ├── userRepository.js
│ └── productRepository.js
├── /middleware
│ ├── authMiddleware.js
│ └── loggingMiddleware.js
├── /config
│ └── databaseConnection.js
Note que as pastas principais, tais como Model, View e Controller, existem na estrutura acima, em conjunto com outras pastas adicionais 😉
Criando uma estrutura MVC com NodeJS
Vamos começar criando uma pasta do nosso projeto, no meu caso, eu criei uma pasta chamada de EstruturaMVC dentro da minha área de trabalho (desktop):
Com o seu terminal (Prompt de Comando) aberto na pasta raiz que acabamos de criar, precisamos inicializar o nosso projeto por meio do NPM
, sendo assim, execute o seguinte comando abaixo:
npm init -y
A flag -y
, como você já deve saber, cria um novo projeto de forma enxuta, respondendo SIM para tudo 😅
Por fim, não se esqueça de criar seu index.js
, com uma mensagem bem legal de boas vindas:
console.log('Olá Mundo!');
Com a configuração incial concluída, você vai precisar criar 3 pastas dentro da sua pasta raiz, uma pasta chamada view, outra chamada model e outra chamada controller (você também pode criá-las no plural, como models, views controllers):
E pronto, o seu projeto já está seguindo 20% da estrutura MVC... os outros 80% dependem exclusivamente da lógica contida em cada arquivo das 3 pastas, ou seja, se a lógica dos seus arquivos cumprem as regras do MVC 😉
De resto, basta aplicar todos os conceitos que você aprendeu na jornada Javascript e também na Jornada NodeJS dentro dessa estrutura (MVC).
Se você pensa que o conteúdo acabou, você está muuuuito enganado! Mas é claro que eu não te deixaria jogado as traças, como quem diz: "Agora se vira aí"... era óbvio que iríamos fazer um projetinho rs
Criando um projeto simples usando a estrutura MVC
Agora é o momento de criarmos um projeto bem simples usando a estrutura que acabamos de aprender 🥳
No caso deste projeto, é de vital importância que você tenha um bom entendimento sobre:
Que são as ferramentas que iremos utilizar para crar este projeto 😉
A ideia principal deste projeto, é termos uma aplicação que irá mostrar todos os nomes e sobrenomes do usuários registrados no banco de dados (pessoa).
Além disso, o usuário terá a possibilidade de adicionar mais registros no banco, como novos nomes e sobrenomes.
Instalando nossas bibliotecas
Dentro da pasta raiz do projeto que está atrelado a esta lição (criamos em tópicos anteriores), abra o terminal (Prompt de Comando) na pasta raiz, e digite o seguinte código:
npm install express express-handlebars mysql2 sequelize dotenv
O comando acima irá instalar todas as bibliotecas necessárias para uma boa execução do nosso projeto!
Criando nossa estrutura de pastas e arquivos
Como estamos utilizando MVC, é bem provável que você já tenha as pastas principais, que são: model, view e controller.
Além dessas pasta ,vamos criar mais três: config, routes e services:
Dentro da pasta Model ficarão todos as entidades do Sequelize.
Dentro da pasta view ficarão todas as telas do handlebars.
Dentro da pasta controller, ficarão todas as classes que serão chamadas pelas rotas.
Dentro da pasta config, ficará o arquivo de conexão com o banco de dados MySQL.
Dentro da pasta routes ficará as rotas principais da nossa aplicação.
Dentro da pasta services, ficarão os arquivos utilitários.
Criando a lógica dos arquivos
A estrutura do nosso projeto será baseada nos seguintes arquivos:
/EstruturaMVC
│ .env
│ index.js
│ package.json
│
├───config
│ connectionDB.js
│
├───controller
│ mainController.js
│
├───model
│ Pessoa.js
│
├───routes
│ mainRoutes.js
│
├───service
│ pessoaService.js
│
└───view
home.handlebars
Feito isso, vamos inserir a lógica de funcionamento dentro de cada um deles:
.env
:
DB_HOST=localhost
DB_USER=root
DB_PASS=.....
DB_NAME=nodejs
DB_DIALECT=mysql
PORT=3000
config > connectionDB.js
:
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASS,
{
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT,
}
);
module.exports = { sequelize };
controller > mainController.js
:
const PessoaService = require('../services/PessoaService');
const getHome = async (req, res) => {
try {
// Buscar todas as pessoas
const pessoas = await PessoaService.listarPessoas();
// Extrair os dataValues de cada instância
const listaPessoas = pessoas.map(pessoa => pessoa.dataValues);
console.log(listaPessoas);
res.render('home', { pessoas: listaPessoas });
} catch (error) {
res.status(500).send('Erro ao carregar pessoas');
}
};
const postPessoa = async (req, res) => {
const { nome, sobrenome } = req.body;
try {
// Salvar pessoa
await PessoaService.criarPessoa(nome, sobrenome);
res.redirect('/');
} catch (error) {
res.status(500).send('Erro ao salvar pessoa');
}
};
module.exports = { getHome, postPessoa };
model > Pessoa.js
:
const { Sequelize, DataTypes } = require('sequelize');
const { sequelize } = require('../config/connectionDB');
const Pessoa = sequelize.define('Pessoa', {
nome: {
type: DataTypes.STRING,
allowNull: false,
},
sobrenome: {
type: DataTypes.STRING,
allowNull: false,
}
});
module.exports = Pessoa;
routes > mainRoutes.js
:
const express = require('express');
const router = express.Router();
const { getHome, postPessoa } = require('../controller/mainController');
router.get('/', getHome);
router.post('/add', postPessoa);
module.exports = router;
services > pessoaService.js
:
const Pessoa = require('../model/Pessoa');
const listarPessoas = async () => {
return await Pessoa.findAll();
};
const criarPessoa = async (nome, sobrenome) => {
return await Pessoa.create({ nome, sobrenome });
};
module.exports = { listarPessoas, criarPessoa };
view > home.handlebars
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boas Vindas</title>
</head>
<body>
<h1>Boas vindas!</h1>
<h2>Lista de Pessoas</h2>
<ul>
{{#each pessoas}}
<li>{{this.nome}} {{this.sobrenome}}</li>
{{/each}}
</ul>
<h2>Adicionar Pessoa</h2>
<form action="/add" method="POST">
<label for="nome">Nome:</label>
<input type="text" id="nome" name="nome" required>
<br>
<label for="sobrenome">Sobrenome:</label>
<input type="text" id="sobrenome" name="sobrenome" required>
<br>
<button type="submit">Enviar</button>
</form>
</body>
</html>
index.js
:
require('dotenv').config();
const express = require('express');
const exphbs = require('express-handlebars');
const mainRoutes = require('./routes/mainRoutes');
const { sequelize } = require('./config/connectionDB');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware para tratar formulários
app.use(express.urlencoded({ extended: true }));
//Troca a pasta views por view
app.set('views', path.join(__dirname, 'view'));
// Configurar Handlebars
app.engine('handlebars', exphbs.engine({
defaultLayout: false // Desativa o uso de layouts padrão
}));
app.set('view engine', 'handlebars');
// Rotas
app.use('/', mainRoutes);
// Testar conexão com o banco de dados
sequelize.authenticate()
.then(() => {
console.log('Conectado ao banco de dados!');
// Sincroniza o modelo com o banco de dados
return sequelize.sync(); // Isso cria as tabelas se elas não existirem
})
.then(() => {
app.listen(PORT, () => console.log(`Servidor rodando na porta ${PORT}`));
})
.catch(err => console.log('Erro ao conectar ao banco de dados: ', err));
Não se esqueça de alterar os dados informados no arquivo .env
para seus dados reais.
Se você não tiver a tabela pessoas
em seu banco de dados, não se preocupe, pois estamos usando sequelize.sync()
no index.js
.
De resto, toda a lógica contida em cada um desses arquivos, já foi vista em lições anteriores, bastando apenas utilizar o comando abaixo para rodar seu projeto 😀
node ./index.js
Veja como ficou o resultado final:
Incrível, não acha? 🤩
Arquivos da lição
Os arquivos que você viu durante o decorrer desta lição, podem ser encontrados neste link.
Conclusão
Nesta lição, você aprendeu a usar o padrão MVC (Modal-View-Controller) em suas aplicações feitas com NodeJS.
Sinta-se a vontade para consultar esta lição sempre que desejar, e até a próxima 😁