Esta é parte 2 do tutorial de como fazer um servidor utilizando NodeJS, Express e um arquivo JSON como banco de dados. Caso você não tenha visto a primeira parte acesse: Criando um servidor em NodeJS - Parte 1
Repositório https://github.com/devbaraus/post_server_node
Meu site baraus.dev
Tabela de conteúdos
- Utilizando o Git
- Criando arquivo server
- Botando a mão na massa
- Manipulando o arquivo json
- Reorganização de código
- Recapitulando
Utilizando o Git
Lembre-se de a cada alteração importante no código fazer um commit. Isso permite que caso algo dê errado seja possível recuperar a última versão correta do código.
Para isso utilize os comandos abaixo:
git add . # adiciona todos os arquivos alterados
git commit -am "..." # adicionar uma mensagem ao commit
git push # caso esteja usando github
Criando arquivo server
Já temos nossa organização de pastas criada, package.json criado, pacotes instalados, agora falta apenas criarmos o arquivo server.js e começarmos a codar! :laptop:
Para isso precisamos criar o arquivo server.js dentro da pasta src
touch src/server.js # cria o arquivo dentro da pasta sem precisar entrar nela
Criado o primeiro arquivo do projeto, vamos utilizar o editor de código
VS Code. Para abrirmos a pasta atual no VS Code utilizando o terminal execute o próximo comando
code . # Abre o editor vscode na pasta atual
Botando a mão na massa
Com a pasta server_node aberta no VS Code como diretório raiz do projeto, procure pelo arquivo server.js dentro da pasta src.
Agora é preciso apenas começar a codar, para isso importamos os pacotes que instalamos anteriormente.
Importe o pacote Express e atribua ele a uma variável, no nosso caso app, como uma função.
Agora, diga ao app escutar requisições na porta 3000, como está abaixo.
// importa pacote express
const express = require('express')
// atribui à app como uma função
const app = express()
// inicia servidor escutando requisições na porta 3000
app.listen(3000, () => {
console.warn(`Servidor escutando na porta 3000`)
})
Agora nosso projeto realmente começou, temos um servidor que escuta na porta 3000 da nossa máquina, porém, ainda falta alguns passos para conseguirmos receber algo.
Vamos configurar mais alguns pacotes para trabalhar junto ao Express.
- importamos o cors, pacote que faz com que outras aplicações consigam se comunicar com nosso servidor
- importamos o morgan, gerador de logs de requição.
- falamos para o app/express utilizar um formato json para o corpo das requisições HTTP
- falamos para o app/express utilizar o cors em sua execução
- falamos para o app/express utilizar o morgan em sua execução
- finalmente, criamos uma rota com o método GET que retorna 'ok'
// importa pacote express
const express = require('express')
// importa pacote cors
const cors = require('cors')
// importa pacote morgan
const morgan = require('morgan')
// atribui a variavel o express como uma função
const app = express()
// app usa corpo em json
app.use(express.json())
// app usa cors
app.use(cors())
// app usa gerador de log morgan
app.use(morgan('dev'))
// rota :GET /
app.get('/', (request, response) => {
return response.send('ok')
})
// inicia servidor escutando requisições na porta 3000
app.listen(3000, () => {
console.warn(`Servidor inicializador na porta 3000`)
})
Criamos o básico básico de um servidor em node, porém até agora não executamos nosso servidor nenhuma vez. Para isso, no terminal, execute o script start que criamos.
yarn start
Este comando faz com que o NodeJS execute o arquivo server.js. Como um servidor é um programa que fica sempre em execução, apenas interrompido quando há algum erro ou outro programa/usuário força sua interrupção, ele ficará esperando requisições. Portanto, faça uma requisição, pelo próprio navegador, na rota http://localhost:3000. Caso nada tenha dado errado, você receberá um ok na página, em formato HTML.
Usando yarn start
nosso servidor nunca para de funcionar, porém também não se reinicia quando fizermos alguma alteração no código, para isso preparamos o script dev. Portanto, interrompa a execução do servidor usando as teclas de atalho CTRL + C
no dentro do terminal e execute um novo comando usando yarn:
yarn dev
Manipulando o arquivo json
Já que programamos nossa primeira rota, é hora de realmente retornar ao usuário dados como uma API Rest.
Separei um arquivo json chamado facts.json, com fatos sobre gatos, para funcionar como nosso bancos nesse projeto. Baixe o arquivo e coloque-o na pasta db do nosso projeto e importe dois módulos padrões do NodeJS, abaixo dos antigos imports dentro do arquivo server.js:
- path, provê métodos para facilmente trabalhar com caminhos dentro do node
- fs, provê métodos para trabalhando com o sistema de arquivos do sistema operacional
// importa módulo path
const path = require('path')
// importa módulo fs
const fs = require('fs')
Logo, utilizamos o modulo path para resolver o caminho relativo do arquivo server.js ao arquivo facts.json e guardamos na variável dbPath.
Dentro do antigo app.get(...) criamos uma nova funcionalidade.
- Lemos o arquivo facts.json utilizando o método readFileSync do módulo fs (sempre retorna string)
- Fazemos o parse/transformação da string para o formato json
- Retornamos o json para o usuário
É pelo navegador, acesse http://localhost:3000/ e veja os mesmos dados do arquivo json sendo mostrado.
// guardamos o caminho para o arquivo json
const dbPath = path.resolve(__dirname, './db/facts.json')
// rota :GET /
app.get('/', (request, response) => {
// Lê de forma síncrona o arquivo json, como string
const data = fs.readFileSync(
dbPath,
'utf8',
)
// transforma a string em json
const facts = JSON.parse(data)
// retorna o json para o usuário
return response.json(facts)
})
Neste ponto é possível ver como funciona um servidor API Rest, o usuário faz uma requisição e o retorno é apenas em json, nada de HTML.
Nosso código está bem enxuto, e queremos isto, algo simples, de fácil entendimento, porém que resolva o proposto. Porém, não estamos tratando nenhuma exceção ou erro que possa acontecer durante a execução.
Para resolver esse problema vamos envolver todo o conteúdo dentro app.get(...)
em um try/catch.
// rota :GET /
app.get('/', (request, response) => {
try{
// Lê de forma síncrona o arquivo json, como string
const data = fs.readFileSync(
dbPath,
'utf8',
)
// transforma a string em json
const facts = JSON.parse(data)
// retorna o json para o usuário
return response.json(facts)
} catch (e) {
}
})
Dessa forma, quando estiver algum erro podemos mandar algum status de resposta http para o usuário. Mas ainda não terminamos, se tudo der certo precisamos enviar um status de resposta ao usuário de código 200, e caso dê algum problema durante a execução do nosso código precisamos tratar de alguma forma e enviar um status de resposta 500.
Para isso utilizaremos alguns status de reposta:
status | quando |
---|---|
200 | Fatos encontrados |
500 | Erro no servidor |
// rota :GET /
app.get('/', (request, response) => {
try{
// Lê de forma síncrona o arquivo json, como string
const data = fs.readFileSync(
dbPath,
'utf8',
)
// transforma a string em json
const facts = JSON.parse(data)
// retorna o json para o usuário com status 200
return response.status(200).json(facts)
} catch (e) {
// print mensagem de erro no terminal
console.log(e)
// retorna mensagem de erro para o usuário com status 500
return response.status(500).json({erro: 'Erro de execução!'})
}
})
CRUD
A partir deste momento já estamos criando um CRUD (Criar, Ler, Alterar e Deletar).
No passo anterior, criamos a leitura de todos os dados, sem nenhuma especifidade. Então, no próximo criaremos a leitura de um dado apenas, baseado na rota que o usuário acessar, o ID.
LER
app.get('/:id', (request, response) => {...})
Para isso utilizamos os método GET novamente, porém, utilizaremos uma rota dinâmica com :id. Isto significa que agora conseguimos acessar http://localhost:3000/1 ou http://localhost:3000/2, e este número adicional na rota nos dará a capacidade de retornarmos ao usuário o fato de ID igual ao inserido por ele.
Bora codar a requisição do usuário para um fato com ID.
status | quando |
---|---|
200 | Fatos encontrados |
404 | Nenhum fato for encontrado |
500 | Erro no servidor |
// ouve requisições com metodo GET com um parâmetro
app.get('/:id', (request, response) {
// pega o ID requisição
const { id } = request.params
try {
// Lê de forma síncrona o arquivo json, como string
let data = fs.readFileSync(dbPath, 'utf8')
// inicializa uma variável nula
let fact = null
// transforma a string em json e pega o array facts
data = JSON.parse(data)['facts']
// passa por todos os fatos
for (let index in data) {
// se encontrar um fato com o mesmo ID que o usuário pediu
if (data[index]['id'] == id) {
// a variavel fact recebe o fato com ID
fact = data[index]
// para o loop
break
}
}
// caso a variável não tenha recebido nenhum fato
if (fact === null) {
// retorne uma mensagem de erro com o status 400
return response
.status(404)
.json({ erro: 'Nenhum fato foi encontrado!' })
}
// retorne o fato encontrado para o usuário
return response.json(fact)
} catch (e) {
// print do erro no terminal
console.log(e)
// retorne uma mensagem de erro com o status 500
return response
.status(500)
.json({ erro: 'Não foi possível executar esta operação!' })
}
}
Temos as duas requisições com o método GET, para quando o usuário pedir todos os fatos e quando pedir apenas um fato com um específico ID.
CRIAR
Precisamos possibilitar que o usuário seja capaz de criar um novo fato.
No corpo da requisição pegaremos todos os campos necessários para criar um novo fato, neste caso, um campo de nome text.
O algoritmo, de forma ampla, para essa funcionalidade é:
- ouvir requisições com o método POST
- pegar campo text do corpo da requisição
- ler arquivo e guardar em uma variável
- criar um objeto com as propriedades necessárias, id, text, type e upvotes
- adicionar o novo fato à variável com os dados do arquivo .json
- sobrescrever o arquivo
- retornar o novo fato ao usuário
status | quando |
---|---|
201 | Fato criado |
500 | Erro no servidor |
// ouve requisições com metodo POST
app.post('/', (request, response) => {
// lê o campo text do corpo da requisição
const { text } = request.body
try {
// Lê de forma síncrona o arquivo json, como string
let data = fs.readFileSync(dbPath, 'utf8')
// transforma a string em json
data = JSON.parse(data)
// cria um novo fato
const newFact = {
id: String(data['facts'].length + 1),
text: text,
type: 'cat',
upvotes: 0,
}
// adiciona o fato ao array de fatos
data['facts'].push(newFact)
// sobrescreve o arquivo
fs.writeFileSync(dbPath, JSON.stringify(data))
// retorna o fato criado ao usuário com o status 201
return response.status(201).json(newFact)
} catch (e) {
// print do erro no terminal
console.log(e)
// retorne uma mensagem de erro com o status 500
return response
.status(500)
.json({ erro: 'Não foi possível executar esta operação!' })
}
})
ALTERAR
Já que criamos, precisamos possibilitar que seja alterado algum dado existente, a partir de um ID. Portanto, dessa vez, iremos possibilitar que o usuário altere algum fato em nosso arquivo/banco a partir da rota dinâmica com ID e um corpo com campo text.
O algoritmo, de forma ampla, desta vez é:
- ouvir requisições com o método PUT e ID
- pegar campo text do corpo da requisição
- ler arquivo e guardar em uma variável
- criar um objeto recebendo o fato existente e alterando o campo text
- adicionar o fato alterado à variável com os dados do arquivo .json
- sobrescrever o arquivo
- retornar o fato alterado ao usuário
status | quando |
---|---|
201 | Fato criado |
404 | Fato não encontrado |
500 | Erro no servidor |
// ouve requisições com método PUT e ID
app.put('/:id', (request, response) => {
// pega o ID da rota
const { id } = request.params
// pega o campo text do corpo da requisição
const { text } = request.body
try {
// Lê de forma síncrona o arquivo json como string
let data = fs.readFileSync(dbPath, 'utf8')
// inicializa duas variáveis como nulas
let fact = null
let indexFact = null
// transforma a string em json
data = JSON.parse(data)
// passa por todos os fatos
for (let index in data['facts']) {
// se encontrar um fato com o mesmo ID que o usuário pediu
if (data['facts'][index]['id'] == id) {
// variável fact recebe o fato com ID
fact = data['facts'][index]
// guarda o index do fato em questão
indexFact = index
// para o loop
break
}
}
// se a variável continua nula
if (fact === null) {
// retorne uma mensagem de erro com o status 404
return response
.status(404)
.json({ erro: 'Nenhum fato foi encontrado!' })
}
// cria um objeto com o fato existente e altera o campo text
const updatedFact = {
...data['facts'][indexFact],
text: text,
}
// guarda o objeto atualizado ao array de fatos
data['facts'][indexFact] = updatedFact
// sobrescreve o arquivo
fs.writeFileSync(dbPath, JSON.stringify(data))
// retorna o fato atualizado com o status 200
return response.status(200).json(updatedFact)
} catch (e) {
// print do erro no terminal
console.log(e)
// retorne uma mensagem de erro com o status 500
return response
.status(500)
.json({ erro: 'Não foi possível executar esta operação!' })
}
})
DELETAR
Finalmente, precisamos possibilitar ao usuário a funcionalidade de deletar um fato existe. Esta funcionalidade segue a mesma ideia da alteração, precisando do ID da rota, porém sem nenhum corpo.
O algoritmo dessa funcionalidade, de forma ampla, é:
- ouvir requisições com o método DELETE e ID
- ler arquivo e guardar em uma variável
- remover o fato com ID do array
- sobrescrever o arquivo
- retornar o um status ao usuário
status | quando |
---|---|
204 | Fato deleteado |
404 | Fato não encontrado |
500 | Erro no servidor |
// ouve requisições com o método DELEte e ID
app.delete('/:id', (request, response) => {
// pega o ID da rota
const { id } = request.params
try {
// Lê de forma síncrona o arquivo json como string
let data = fs.readFileSync(dbPath, 'utf8')
// inicializa uma variável como
let indexFact = null
// transforma a string em json
data = JSON.parse(data)
// passa por todos os fatos
for (let index in data['facts']) {
// se encontrar um fato com o mesmo ID que o usuário pediu
if (data['facts'][index]['id'] == id) {
// guarda o índice do fato em questão
indexFact = index
// para o loop
break
}
}
// se a variável continua nula
if (indexFact == null) {
return response
.status(404)
.json({ erro: 'Nenhum fato foi encontrado!' })
}
// remove um elemento do array a partir do índice
data['facts'].splice(indexFact, 1)
// sobrescreve o arquivo
fs.writeFileSync(dbPath, JSON.stringify(data))
// retorna o status 204
return response.sendStatus(204)
} catch (e) {
// print do erro no terminal
console.log(e)
// retorne uma mensagem de erro com o status 500
return response
.status(500)
.json({ erro: 'Não foi possível executar esta operação!' })
}
})
Reorganização de código
Criando controller
Se você olhar para seu arquivo server.js ele está enorme e é difícil sua leitura, além de que eventualmente nós podemos querer acrescentar mais funcionalidades, portando, mais código ao nosso projeto.
Para uma melhor manutenção é importante separarmos aquilo que é de inicialização/configuração do servidor do que é funcionalidade para o usuário.
Então, nessa fase iremos reorganizar nossos arquivos e fazer uso da pasta controllers criada anteriormente ainda na parte 1.
Para começar, vamos criar um arquivo chamado FactsController.js dentro da pasta controllers.
Dentro deste arquivo importaremos os módulo path e fs, podemos apenas mover os importes do arquivo server.js; Moveremos a variável dbPath para dentro deste arquivo, ajustando o caminho; Criaremos uma classe com nome FactsController e dentro dessa classe criaremos 5 métodos, index, show, create, update e delete, todos com os mesmo parâmetros, request e response, e ao final exportaremos a classe como um módulo.
// importa módulo path
const path = require('path')
// importa módulo fs
const fs = require('fs')
// guardamos o caminho para o arquivo json
const dbPath = path.resolve(__dirname, '../db/facts.json')
class FactsController{
index(request, response){
}
show(request,response){
}
create(request,response){
}
update(request,response){
}
delete(request,response){
}
}
modules.export = FactsController
O próximo passo para organizarmos nosso código é mover algumas partes de código que estão no server.js para este arquivo. Portanto, todo o código dentro de app.get('/', (request, response){...})
ficará dentro de index, assim:
index(request, response) {
try {
// Lê de forma síncrona o arquivo json, como string
const data = fs.readFileSync(dbPath, 'utf8')
// transforma a string em json
const facts = JSON.parse(data)
// retorna o json para o usuário com status 200
return response.status(200).json(facts)
} catch (e) {
// print do erro no terminal
console.log(e)
// retorne uma mensagem de erro com o status 500
return response
.status(500)
.json({ erro: 'Não foi possível executar esta operação!' })
}
}
O mesmo será feito para o outro GET, POST, PUT e DELETE. Seguindo o esquema abaixo.
app.get('/:id', (request, reponse)=>{...}) -> show(request, response){...}
app.post('/', (request, reponse)=>{...}) -> create(request, response){...}
app.put('/:id', (request, reponse)=>{...}) -> update(request, response){...}
app.delete('/:id', (request, reponse)=>{...}) -> delete(request, response){...}
Criando sistema de rotas
Nosso controller agora não está se comunicando com o servidor/Express e para deixar nosso código ainda mais limpo criaremos um arquivo chamado routes.js no mesmo diretório do arquivo server.js. Este arquivo irá conter todas as rotas do nosso servidor, podendo, a medida que o servidor for crescendo, ser dividido em mais arquivos.
Nesse arquivo de rotas iremos importar o arquivo FactsController.js como um módulo, usando ./controllers/FactsController para sinalizar que é um módulo criado no projeto. Importaremos também o módulo Express, porém, dessa vez iniciaremos um roteador, e não um servidor; Criaremos nossas rotas e exportaremos o arquivo como um módulo.
const router = require('express').Router()
const FactsController = require('./controllers/FactsController')
const factsController = new FactsController()
// Retorna todos fatos
router.get('/', factsController.index)
// Retorna um fato
router.get('/:id', factsController.show)
// Cria um novo fato
router.post('/', factsController.create)
// Edita um fato
router.put('/:id', factsController.update)
// Deleta um fato
router.delete('/:id', factsController.delete)
module.exports = router
Limpando o server.js
Dentro do arquivo server.js, agora temos códigos que não estão mais sendo usados pelo servidor. Então vamos dar uma limpa e colocar nosso servidor para funcionar novamente!
Exclua todos os app.get
, app.post
, app.put
e app.delete
, importe o arquivo de rotas criado anteriormente e fale para o servidor usar esse arquivo de rotas .
// importa pacote express
const express = require('express')
// importa pacote cors
const cors = require('cors')
// importa pacote morgan
const morgan = require('morgan')
// importa rotas pelo arquivo routes.js
const routes = require('./routes')
// atribui a variavel o express como uma função
const app = express()
// app usa corpo em json
app.use(express.json())
// app usa cors
app.use(cors())
// app usa gerador de log morgan
app.use(morgan('dev'))
// utilize o arquivo de rotas
app.use('/', routes)
// inicia servidor escutando requisições na porta 3000
app.listen(3000, () => {
console.warn(`Servidor inicializador na porta 3000`)
})
Recapitulando
Neste ponto nosso projeto chega ao fim, criamos todas rotas de um CRUD, manipulamos o arquivo JSON e organizamos nossos arquivo de um maneira que seja fácil a manutenção, ainda não é o ideal, mas é o suficiente!
Se você chegou até aqui espero que tenha aprendido como criar um servidor NodeJS e consigar criar o seu próprio sem grandes dificildades.
Estas duas partes foram meus primeiros posts, ainda pretendo criar uma série de posts envolvendo o desenvolvimento de aplicações Back End e Front End.
Deixe seu comentário dizendo o que está bom e o que é preciso melhorar nos posts.
Top comments (0)