NodeJS e Express | programaDev
06 de abril de 2021 • 12 min de leitura
Construindo uma API REST do zero
Neste artigo será mostrado como criar um projeto com NodeJS e Express e vamos expor uma API para podermos realizar as quatro operações básicas de um CRUD (criar, ler, atualizar e deletar dados). Também será mostrado como podemos construir um projeto de forma simples, descomplicada, com baixa acoplamento e alta coesão entre seus componentes por meio de injeção de dependências e inversão de controle.
Fazendo uma breve introdução sobre as tecnologias que serão apresentadas nesse artigo, primeiramente temos o NodeJS que é um projeto open-source criado para ser um ambiente de desenvolvimento backend escrito em JavaScript, ele explora os benefícios que o JavaScript possui, como orientação a eventos e assíncronicidade.
Juntamente com o NodeJS usaremos nesse projeto o Express que é um framework para desenvolvimento de aplicações web minimalista, isso significa que ele é bem leve e simples mas que não trás consigo todas as funcionalidades por padrão de um servidor web e isso é uma grande vantagem do Express pois é um dos motivos pelo qual ele é muito flexível, e por meio de middlewares é possível plugar libs e ferramentas que nos ajudam no desenvolvimento.
O projeto consistirá em uma agenda de contatos, onde poderemos criar um contato novo, buscar um contato ou todos, editar um existente e deletar um contato.
Existem algumas maneiras de criar um projeto com Express, o próprio Express possui um cli para criação.
Aqui iremos fazer de uma forma que eu considero mais simples que é criar via command line* com **NPM.
Vamos criar uma pasta chamada phonebook e após isso, criar o projeto usando NPM:
mkdir phonebook && cd phonebook
Com isso temos a estrutura básica do projeto que nada mais é do que um arquivo package.json:
Vamos aproveitar e instalar as dependências que vamos precisar para iniciar esse projeto:
E também as dependências que usaremos posteriormente para subir o nosso server no ambiente de local de desenvolvimento e testes:
npm install --save-dev nodemon jest supertest
Agora falta criarmos o arquivo que será executado quando iniciarmos a aplicação, vamos chamá-lo de index.js:
Só com isso podemos executar o node chamando o arquivo index.js que deverá funcionar:
> phonebook@1.0.0 dev /Users/guilherme/develop/repo/phonebook
A API está funcionando!
Decidi começar pela definição de modelo, porque apesar desse modelo ser simples eu entendo que é sempre bom deixar essa base pronta pois fica mais fácil construir a aplicação em volta de um domínio do que o contrário. Acredito que utilizar outras tecnologias fica mais flexível de mudar do que a mudança no domínio nessa abordagem.
Então iremos criar uma pasta chamada model e nela a index.js:
A definição acima é a representação do que seria um contato em uma agenda de contatos composta pelo id, (aqui entra um discussão sobre Entidades e VO mas para esse exemplo deixei com um id pois acredito que o modelo em um projeto real não deveria possuir um id provavelmente voltaremos a esse ponto em um artigo futuro) name, telephone e address que são Strings e no exports criamos esse objeto com a função create.
Criando a Repository
Após ter criado o domínio vamos criar a nossa Repository que será responsável por lidar com a persistência de dados. Você pode ter percebido que até o momento não adicionamos nenhuma dependência de persistência então como iremos criar o comportamento responsável por isso?
Vamos aqui simplificar um pouco as coisas e iremos criar uma persistência em memória e mais a frente vamos entender como podemos deixar tudo bem simples e desacoplado usando a Injeção de Dependências e Inversão de Controle.
Vamos criar uma pasta chamada repository e dentro dela o nosso arquivo index.js:
class InMemoryRepository{
this._data.push(contact)
selectById(id){
return this._data.find(c => c.id === id)
update(id, contact){
const elementId = this._data.findIndex(element => element.id === id);
const updateContact = Object.assign(this._data[elementId], contact)
this._data[elementId] = updateContact
return this._data[elementId]
const index = this._data.findIndex(element => element.id === id)
this._data.splice(index, 1)
module.exports = InMemoryRepository
Foi usada uma abordagem de classe aqui justamente para depois podermos usar a Injeção de Dependências, mas podemos ver também que temos uma variável membro chamada _data que é um array e temos as funções que irão fazer as nossas operações de CRUD em cima desse array.
Após isso exportamos a nossa classe InMemoryRepository.
Agora chegou a hora de criar a camada da aplicação que é responsável por executar a lógica de negócios.
Vamos criar uma pasta chamada service e dentro dela o arquivo index.js:
this.repository.insert(body)
return this.repository.selectById(parseInt(id, 2))
return this.repository.selectAll()
put(id, body){
return this.repository.update(parseInt(id, 2), body)
this.repository.remove(parseInt(id, 2))
Aqui também usamos a abordagem de classe, mas por que?
Pois assim é possível injetar a dependência do repository no construtor e com isso o controle é invertido já que a Service desconhece qual será a implementação a ser usada, a única coisa que importa para a Service é que a repository que será passada deve possuir as funções de insert, selectById, selectAll, update e remove. Não é responsabilidade da Service saber se a repository é um banco em memória, MongoDB, Postgres ou qualquer outro meio de persistir dados.
Caso seja necessário no futuro implementar alguma outra ação ou mude a lógica de negócios, esta alteração deve ser implementada na Service e caso necessite de outra dependência deve ser adicionado ou injetado no construtor da classe.
Vamos criar as rotas da nossa aplicação, aqui iremos definir quais verbos HTTP iremos deixar disponíveis e que iremos direcionar as requisições quando elas chegarem.
const InMemoryRepository = require('../repository')
const service = new Service(new InMemoryRepository())
const result = service.getById(id)
const result = service.getAll()
const result = service.put(id, body)
service.remove(id)
router.options('/', (req, res) => {
Vamos por partes pra entender tudo o que está no código acima:
Nesse trecho importamos do próprio Express a dependência do Router que irá disponibilizar aqui os verbos HTTP, importamos aqui as classes InMemoryRepository e Service e em seguida instânciamos a Service e passamos a dependência de uma Repository para ela que nesse caso será a InMemoryRepository.
Aqui usamos o router e chamamos o método post e passamos qual será o path ou caminho que será exposta na API, aqui deixamos com '/'para indicar que não queremos passar nada na url só de chamar um POST ele será atendido por esse método.
A função post trás consigo o request e o response e com isso podemos extrair algumas informações importantes no request e adicionar dados no response.
No exemplo acima conseguimos pegar o body que é enviado na requisição e após executar a lógica na Service adicionar o status e o body no response.
Aqui abaixo temos as implementações do GET:
O interessante aqui é entender que no request também conseguimos pegar parâmetros passados na url para isso precisamos de um identificador no path que é passado na função get no caso acima é :id a na função pegamos o valor através da sintaxe req.params.id.
A lógica nas requisições GET é que caso não encontre dados na consulta retorne o status 204 - No Content e caso encontre retorna 200 - Ok com os dados solicitados.
Os métodos para PUT e DELETE seguem a mesma lógica.
Configurando o Express e middlewares
Temos a Service e as Rotas configuradas e agora é necessário adicionar o módulo de rotas ao Express para que ele consiga utilizar e assim ficar disponível para ser usado.
No arquivo index.js na raiz do projeto já existe uma configuração:
Com essa configuração já estamos usando os middlewares, onde adicionamos as funções que queremos complementar ao Express, acima estamos usando a lib body-parser para ajudar com o parse da resposta e outro middleware para tratativa de CORS e vamos adicionar o nosso módulo de rotas:
Acima foi importado o módulo router e adicionado no Express através da função use onde definimos o path raiz da nossa API e no segundo argumento o módulo router.
Podemos iniciar a aplicação dessa forma:
E fazendo um POST:
"name" : "Kelly",
"address" : "Rua dos Bobos n 1",
"telephone" : "118888888"
No começo do artigo adicionamos as dependências do jest e supertest e agora vamos implementar um teste.
Na pasta router vamos criar o arquivo router.test.js, seguindo a convenção de nomenclatura do jest para que ele saiba quais arquivos devem ser testados.
Dentro do arquivo vamos criar a nossa primeira suite de testes para testar a rota de POST:
const server = require('../index')
describe('Make requests to the server', () => {
it('Should create a contact', async () => {
const resp = await supertest(server).post('/api').send({
expect(resp.statusCode).toEqual(201)
expect(resp.body.name).toEqual("Kelly")
Aqui importamos a lib do supertest e o arquivo index.js da raíz do projeto, primeiramente adicionamos uma função chamada afterAll para que após o testes serem rodados a aplicação seja terminada.
Criamos a suite de testes com a função describe e dentro dela colocamos os testes necessários para testar aquela suite com a função it.
Para fazer o mock da requisição usamos o supertest a passamos para ele o nosso server, invocamos a função HTTP que queremos testar passando o path e com a função send passamos o json que será enviado.
Com o retorno do response conseguimos fazer as asserções dos testes, nesse caso queremos testar que a cada POST bem sucedido iremos retornar o status code 201 - Created e o body será devolvido então podemos fazer a asserção de algum campo do response.
Agora conseguimos rodar o comando a seguir para rodar esse teste:
E teremos a seguinte resposta:
> jest --coverage --runInBand
PASS  router/route.test.js
Make requests to the server
✓ Should create a contact (65 ms)
at Server.<anonymous> (index.js:16:47)
----------------------|---------|----------|---------|---------|----------------------------------
All files             |   48.68 |        0 |   29.17 |      50 |
phonebook            |     100 |      100 |     100 |     100 |
index.js            |     100 |      100 |     100 |     100 |
phonebook/model      |     100 |      100 |     100 |     100 |
phonebook/repository |      20 |      100 |   22.22 |      25 |
index.js            |      20 |      100 |   22.22 |      25 | 12-35
phonebook/router     |   39.47 |        0 |   14.29 |   39.47 |
index.js            |   39.47 |        0 |   14.29 |   39.47 | 16-24,30-37,43-48,53-57,62,66-69
phonebook/service    |      50 |      100 |   33.33 |      50 |
index.js            |      50 |      100 |   33.33 |      50 | 14-26
Time:        2.015 s
Além do teste passamos o parâmetro --coverage e com isso é gerado um html com um relatório da cobertura dos testes.
Nesse artigo iniciamos a construção de uma API REST do zero usando NodeJS e Express. Vimos a facilidade de usar o Express e como o mecanismo de middleware torna o desenvolvimento flexível e dinâmico. Também conseguimos ver como deixar uma aplicação desacoplada utilizando o conceito de Injeção de dependências