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)