Criando um servidor em NodeJS - Parte 2
Tags: node, javascript, json
Published: 2020-08-23 18:47:21

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](https://dev.to/devbaraus/criando-um-servidor-em-nodejs-parte-1-c7f)

> Repositório [https://github.com/devbaraus/post_server_node](https://github.com/devbaraus/post_server_node)
> Meu site [baraus.dev](https://baraus.dev)

## Tabela de conteúdos
- [Utilizando o Git](#utilizando-o-git)
- [Criando arquivo server](#criando-arquivo-server)
- [Botando a mão na massa](#botando-a-mão-na-massa)
- [Manipulando o arquivo json](#manipulando-o-arquivo-json)
	- [CRUD](#crud)
		- [LER](#ler)
		- [CRIAR](#criar)
		- [ALTERAR](#alterar)
		- [DELETAR](#deletar)
- [Reorganização de código](#reorganização-de-código)
	- [Criando controller](#criando-controller)
	- [Criando sistema de rotas](#criando-sistema-de-rotas)
	- [Limpando o server.js](#limpando-o-serverjs)
- [Recapitulando](#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:

```bash
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**

```bash
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 
<abbr title="Visual Studio Code">VS Code</abbr>. Para abrirmos a pasta atual no VS Code utilizando o terminal execute  o próximo comando

```bash
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.

```js
// 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'**
```js
// 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.
```bash
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:

```bash
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](https://icedrive.net/0/4fzX2PP8GY), 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

```js
// 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.

```js
// 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.

```js
// 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 |

```js
// 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 <abbr title="Create, Read, Update and Delete">CRUD</abbr>  (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

```js
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](http://localhost:3000/1) ou [http://localhost:3000/2](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 |


```js
// 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 |

```js
// 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 |

```js
// 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 |

```js
// 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.

```js
// 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:

```js
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.

```js
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 .

```js
// 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.