Skip to main content

Node JS



Introdução a MVC - Model View Controller


A arquitetura MVC (Model-View-Controller) é um padrão de design de software que é amplamente utilizado para desenvolver aplicativos web, para usar basta separar os arquivos em diretórios com os nomes dos componentes que serão explicados mais abaixo. Ela é composta por três componentes principais:


Model: O modelo representa a camada de dados do aplicativo. Ele lida com a manipulação de dados, como a interação com um banco de dados ou outros recursos externos. O modelo contém as regras de negócio e a lógica para manipular os dados.


View: A visão é responsável por exibir a interface do usuário. Ela renderiza a interface do aplicativo e apresenta os dados fornecidos pelo modelo. A visão não contém lógica de negócios, mas é responsável por exibir os dados de maneira agradável para o usuário.


Controller: O controlador é responsável por receber as solicitações do usuário, processá-las e coordenar a interação entre o modelo e a visão. Ele recebe os dados da solicitação, interage com o modelo para obter os dados necessários e, em seguida, determina qual visão deve ser renderizada para exibir a resposta ao usuário.


Estrutura com MVC:

  • Controllers
    Diretório onde ficam os arquivos de controle.

  • Models
    Diretórios onde ficam os arquivos de Models, interação com banco de dados.

  • Views
    Diretórios onde ficam os arquivos de View (Handlebars).

  • routes
    Diretórios onde ficam os arquivos de rotas.

  • index.js
    Arquivo que inicializa a aplicação.



Criando o Model


Para criar um model, coloque ele dentro do diretório models e no arquivo <model>.js, exporte como um módulo.

models/Task.js
const { DataTypes } = require('sequelize')

const db = require('../db/conn')

const Task = db.define('Task', {
title: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING,
},
done: {
type: DataTypes.BOOLEAN,
},
})

module.exports = Task

Agora importe o model dentro do arquivo base. Assim que rodar o código o banco será criado.

index.js
const express = require('express')
const exphbs = require('express-handlebars')

const app = express()

const conn = require('./db/conn')

const Task = require('./models/Task') // Importe o Model chamado 'Task' (tabela Task no DB)

app.engine('handlebars', exphbs.engine()) // define handlebars como template engine
app.set('view engine', 'handlebars')

app.use(
express.urlencoded({
extended: true,
}),
)

app.use(express.json())

app.use(express.static('public'))

conn
.sync()
.then(() => {
app.listen(3000)
})
.catch((err) => console.log(err))

A tabela é criada por causa do código abaixo:

const conn = require('./db/conn')

conn
.sync()
.then(() => {
app.listen(3000)
})
.catch((err) => console.log(err))


Criando o Controller


Os controllers devem ser colocados em um diretório separado, normalmente esse diretório chama controllers. Ele será uma classe que contém as funções com a lógica de cada Rota. Precisamos importar os Models.

controllers/TaskController.js
const Task = require('../models/Task')

module.exports = class TaskController {
static createTask(req, res) {
res.render('tasks/create')
}

static showTasks(req, res) {
res.render('tasks/all')
}
}

O código acima está criando um controlador chamado TaskController que tem um método estático createTask.


A linha const Task = require('../models/Task') está importando o modelo Task de um arquivo localizado em um diretório acima, dentro da pasta models. Isso indica que o modelo Task contém as informações necessárias para popular a tabela Task no banco de dados.


O método createTask é uma função estática que recebe dois argumentos: req e res. Dentro do método, a função res.render('tasks/create') está sendo chamada. Isso vai ser utilizado em um contexto do Express.js para renderizar um template chamado create. Isso sugere que a intenção é renderizar uma página onde o usuário pode criar uma nova tarefa.


função estática

Uma função normal, como function example() {...}, é uma função que pertence a um objeto ou a um protótipo de objeto. Ela pode ser chamada usando a sintaxe object.example(), onde object é uma instância do objeto que possui a função. Funções normais podem acessar propriedades e métodos do objeto em que estão definidas.


Uma função estática, como static example() {...}, é uma função que pertence a uma classe (classe em JavaScript é uma função) e não a uma instância da classe. Ela pode ser chamada diretamente usando a sintaxe ClassName.example(). Funções estáticas não têm acesso direto às propriedades e métodos da instância, pois são independentes dela.



Criando o Route


Os Routes devem ser colocados em um diretório separado, normalmente esse diretório chama routes. Nele teremos que criar as rotas do sites, importar o Controller e para cada rota, associar uma função estática que foi criada no Controller.

routes/tasksRouters.js
const express = require('express')
const router = express.Router()
const TaskController = require('../controllers/TaskController')

// Criando as rotas e associando a funçao estática do nosso controller:
router.get('/add', TaskController.createTask)
router.get('/', TaskController.showTasks)

module.exports = router

Agora importe as rotas dentro do arquivo base. Defina também o middleware para ser usado com nossas rotas.

index.js
const express = require('express')
const exphbs = require('express-handlebars')

const app = express()

const conn = require('./db/conn')

// Models
const Task = require('./models/Task')

// routes
const taskRoutes = require('./routes/tasksRoutes')

app.engine('handlebars', exphbs.engine()) // define handlebars como template engine
app.set('view engine', 'handlebars')

app.use(
express.urlencoded({
extended: true,
}),
)

app.use(express.json())

app.use(express.static('public'))

app.use('/tasks', taskRoutes) // define um middleware que aplica o conjunto de rotas 'taskRoutes' a todas as rotas que começam com o caminho '/tasks'.
// Para mais detalhes veja: https://www.sysnetbr.eng.br/docs/Programacao/Nodejs/node2#m%C3%B3dulo-de-rotas

conn
.sync()
.then(() => {
app.listen(3000)
})
.catch((err) => console.log(err))


Salvando dados


Para salvar dados, vamos criar uma rota usando método post.

routes/tasksRouters.js
const express = require('express')
const router = express.Router()
const TaskController = require('../controllers/TaskController')

router.get('/add', TaskController.createTask)
router.post('/add', TaskController.createTaskSave)
router.get('/', TaskController.showTasks)

module.exports = router

Agora vamos criar a função estática createTaskSave na classe:

controllers/TaskController.js
const Task = require('../models/Task')

module.exports = class TaskController {
static createTask(req, res) {
res.render('tasks/create')
}

static createTaskSave(req, res) {
const task = {
title: req.body.title,
description: req.body.description,
done: false, // Definindo done manualmente
}

// Chama 'Task' que é o model que cria e alimenta a tabela no banco de dados,
// e informa a var 'task' criada mais acima:
Task.create(task)
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}

static showTasks(req, res) {
res.render('tasks/all')
}
}


Pegando itens do Banco


Para obter dados do banco, vamos criar uma rota usando método get que acessa uma função do controller. Comece criando uma nova função no controller:

controllers/TaskController.js
const Task = require('../models/Task')

module.exports = class TaskController {
static createTask(req, res) {
res.render('tasks/create')
}

static createTaskSave(req, res) {
const task = {
title: req.body.title,
description: req.body.description,
done: false,
}

Task.create(task)
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}

static showTasks(req, res) {
Task.findAll({ raw: true }) // exibe os dados em array
.then((data) => {
res.render('tasks/all', { tasks: data }) // ALL é um handlebars
})
.catch((err) => console.log(err))
}
}

Crie o all.handlebars:

views/tasks/all.handlebars
<h1>Todas as Tasks:</h1>
<ul class="task-list">
{{#each tasks}}
<li>
<a href="/" class="title">{{this.title}}</a>
<span class="actions">
<a href="/"><i class="bi bi-check2"></i></a>
<a href="/"><i class="bi bi-pencil-square"></i></a>
<a href="/"><i class="bi bi-x-lg"></i></a>
</span>
</li>
{{/each}}
</ul>

bi bi-check2 define o ícone, para isso importe o ícone no arquivo de main.handlebars e adicione (dentro de head): <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"> Isso chama bootstrap icons.


A rota já existe router.get('/', TaskController.showTasks), basta acessar a página inicial.



Removendo dados


Para remover dados do banco, vamos criar uma rota usando método destroy que acessa uma função do controller. Vamos usar o método post para enviar os dados de remoção. Comece criando uma nova rota:

routes/tasksRoutes.js
const express = require('express')
const router = express.Router()
const TaskController = require('../controllers/TaskController')

router.get('/add', TaskController.createTask)
router.post('/add', TaskController.createTaskSave)
router.post('/remove', TaskController.removeTask)
router.get('/', TaskController.showTasks)

module.exports = router

Agora vamos criar a função no controller:

controllers/TaskController.js
const Task = require('../models/Task')

module.exports = class TaskController {
static createTask(req, res) {
res.render('tasks/create')
}

static createTaskSave(req, res) {
const task = {
title: req.body.title,
description: req.body.description,
done: false,
}

Task.create(task)
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}

static showTasks(req, res) {
Task.findAll({ raw: true })
.then((data) => {
let emptyTasks = false

if (data.length === 0) {
emptyTasks = true
}

res.render('tasks/all', { tasks: data, emptyTasks })
})
.catch((err) => console.log(err))
}

static removeTask(req, res) {
const id = req.body.id // vamos remover usando o ID da tabela

Task.destroy({ where: { id: id } })
.then(res.redirect('/tasks')) // Redireciona para /task ao final da operação
.catch((err) => console.log())
}
}

Ajuste o handlebars:

views/tasks/all.handlebars
<h1>Todas as Tasks:</h1>
<ul class="task-list">
{{#if emptyTasks}}
<p>Não há tarefas cadastradas</p>
{{/if}}
{{#each tasks}}
<li>
<a href="/" class="title">{{this.title}}</a>
<span class="actions">
<a href="/"><i class="bi bi-check2"></i></a>
<a href="/"><i class="bi bi-pencil-square"></i></a>
<form action="/tasks/remove" method="POST">
<input type="hidden" name="id" value={{this.id}}>
<button type="submit">
<i class="bi bi-x-lg"></i>
</button>
</form>
</span>
</li>
{{/each}}
</ul>


Editando um item da tabela


Para editar dados do banco, precisamos pegar os dados e colocar no formulário em uma view. Para isso vamos usar o Controller, que pegará os dados via Model. Depois disso a gente cria uma rota que corresponda a um ID de uma Task, depois é só preencher os valores dos inputs. Comece criando uma nova rota:

routes/tasksRoutes.js
const express = require('express')
const router = express.Router()
const TaskController = require('../controllers/TaskController')

router.get('/add', TaskController.createTask)
router.post('/add', TaskController.createTaskSave)
router.post('/remove', TaskController.removeTask)
router.get('/edit/:id', TaskController.updateTask)
router.get('/', TaskController.showTasks)

module.exports = router

Agora vamos criar a função no controller:

controllers/TaskController.js
const Task = require('../models/Task')

module.exports = class TaskController {
static createTask(req, res) {
res.render('tasks/create')
}

static createTaskSave(req, res) {
const task = {
title: req.body.title,
description: req.body.description,
done: false,
}

Task.create(task)
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}

static showTasks(req, res) {
Task.findAll({ raw: true })
.then((data) => {
let emptyTasks = false

if (data.length === 0) {
emptyTasks = true
}

res.render('tasks/all', { tasks: data, emptyTasks })
})
.catch((err) => console.log(err))
}

static removeTask(req, res) {
const id = req.body.id

Task.destroy({ where: { id: id } })
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}

static updateTask(req, res) { // coleta os dados da tabela
const id = req.params.id

Task.findOne({ where: { id: id }, raw: true })
.then((data) => {
res.render('tasks/edit', { task: data })
})
.catch((err) => console.log())
}
}

Agora crie o handlebars chamado edit:

views/tasks/edit.handlebars
<h1>Editando a task: {{task.title}}</h1>
<form action="/tasks/edit" method="POST">
<input type="hidden" name="id" value={{task.id}}>
<div class="form-control">
<label for="title">Título:</label>
<input type="text" name="title" placeholder="O que você vai fazer?" value="{{task.title}}">
</div>
<div class="form-control">
<label for="description">Descrição:</label>
<textarea type="text" name="description" placeholder="O que você vai fazer?">{{task.description}}</textarea>
</div>
<input type="submit" value="Editar">
</form>

Agora vamos criar uma nova função no Controller, para que possamos salvar os dados alterados na tabela.

controllers/TaskController.js
const Task = require('../models/Task')

module.exports = class TaskController {
static createTask(req, res) {
res.render('tasks/create')
}

static createTaskSave(req, res) {
const task = {
title: req.body.title,
description: req.body.description,
done: false,
}

Task.create(task)
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}

static showTasks(req, res) {
Task.findAll({ raw: true })
.then((data) => {
let emptyTasks = false

if (data.length === 0) {
emptyTasks = true
}

res.render('tasks/all', { tasks: data, emptyTasks })
})
.catch((err) => console.log(err))
}

static removeTask(req, res) {
const id = req.body.id

Task.destroy({ where: { id: id } })
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}

static updateTask(req, res) {
const id = req.params.id

Task.findOne({ where: { id: id }, raw: true })
.then((data) => {
res.render('tasks/edit', { task: data })
})
.catch((err) => console.log())
}
static updateTaskPost(req, res) {
const id = req.body.id

const task = {
title: req.body.title,
description: req.body.description,
}

Task.update(task, { where: { id: id } })
.then(res.redirect('/tasks'))
.catch((err) => console.log())
}
}

A função updateTaskPost é chamada quando o formulário de edição é submetido. No HTML do formulário de edição, o atributo action do formulário está definido como "/tasks/edit" e o método como POST. Quando o formulário é submetido, ele envia uma requisição POST para a rota "/tasks/edit".


No arquivo routes/tasksRoutes.js, existe essa rota para lidar com a requisição POST!


Essa rota está apontando para a função updateTaskPost do TaskController, que é responsável por receber os dados do formulário de edição e atualizar a entrada correspondente no banco de dados.