DEV Community

Cover image for Criando um servidor em NodeJS - Parte 2
Bruno de Araujo Alves
Bruno de Araujo Alves

Posted on • Edited on

Criando um servidor em NodeJS - Parte 2

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

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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`) }) 
Enter fullscreen mode Exit fullscreen mode

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`) }) 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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') 
Enter fullscreen mode Exit fullscreen mode

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) }) 
Enter fullscreen mode Exit fullscreen mode

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) { } }) 
Enter fullscreen mode Exit fullscreen mode

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!'}) } }) 
Enter fullscreen mode Exit fullscreen mode

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) => {...}) 
Enter fullscreen mode Exit fullscreen mode

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!' }) } } 
Enter fullscreen mode Exit fullscreen mode

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!' }) } }) 
Enter fullscreen mode Exit fullscreen mode

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!' }) } }) 
Enter fullscreen mode Exit fullscreen mode

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!' }) } }) 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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!' }) } } 
Enter fullscreen mode Exit fullscreen mode

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){...} 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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`) }) 
Enter fullscreen mode Exit fullscreen mode

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)