Seu carrinho está vazio no momento!

Introdução
Olá, futuro arquiteto de software! Sejam bem-vindos à Aula 77, onde mergulharemos em uma abordagem de desenvolvimento que transformará a maneira como você constrói suas APIs: o Test-Driven Development (TDD). Prepare-se para uma experiência prática e reveladora, onde a qualidade e a confiança se tornam pilares do seu código.
Imagine-se construindo uma ponte colossal sobre um rio caudaloso. Um engenheiro experiente não começaria a lançar vigas e concreto aleatoriamente. Ele primeiro desenharia plantas detalhadas, faria cálculos exaustivos para cada segmento da ponte e, crucialmente, testaria a resistência de cada parte — a fundação, cada pilar, cada treliça — antes mesmo de iniciar a construção definitiva. Ele quer ter certeza de que cada pequeno componente suportará a carga esperada. Esta é a essência do TDD no desenvolvimento de software. Nós escrevemos o teste primeiro, para garantir que sabemos o que estamos construindo e que cada peça funcional realmente opere como planejado.
Esta metodologia é estratégica para APIs modernas porque viabiliza a criação de sistemas robustos, de fácil manutenção e com documentação implícita através dos testes. Em um cenário de microsserviços e integrações complexas, ter a certeza de que cada endpoint funciona conforme o esperado, mesmo após inúmeras alterações, é de valor inestimável. Garante a resiliência do seu sistema e minimiza surpresas desagradáveis em produção.
Nesta aula, desenvolveremos um pequeno serviço de gerenciamento de produtos utilizando Node.js e Express. Mais especificamente, vamos implementar a funcionalidade de criar um novo produto (POST /products) seguindo estritamente o fluxo TDD. Você verá como cada teste falhando nos guiará para a implementação correta do código, culminando em um endpoint funcional e testado.
Dentro do vasto ecossistema Node.js/Express, o TDD se encaixa perfeitamente, permitindo que desenvolvedores construam APIs de forma mais modular e confiável. Utilizaremos frameworks de teste populares como o Mocha e o Chai, que se integram de maneira fluida com suas aplicações Express.
Conceito Fundamental
O Test-Driven Development (TDD) é uma disciplina de desenvolvimento de software onde os testes são escritos antes do código de produção. Não é apenas uma forma de testar, mas sim uma metodologia de design que orienta a sua programação. O TDD segue um ciclo iterativo e bem definido, conhecido como “Red-Green-Refactor”:
- Red (Vermelho): Escreva um pequeno teste automatizado para uma nova funcionalidade ou uma correção de bug. Este teste deve falhar. Por que falhar? Porque a funcionalidade ainda não foi implementada ou o bug ainda não foi corrigido. Esta falha valida que o teste está de fato testando o que você espera e que não há uma implementação oculta.
- Green (Verde): Escreva a menor quantidade de código de produção necessária para que o teste recém-escrito passe. O objetivo aqui é apenas fazer o teste passar, sem se preocupar excessivamente com a elegância do código ou otimizações.
- Refactor (Refatorar): Uma vez que o teste esteja passando (verde), você pode refatorar o código. Isso significa melhorar a estrutura, legibilidade e eficiência do código, sem alterar seu comportamento. Você tem a segurança de saber que, se algo quebrar durante a refatoração, seu teste (que está verde) ficará vermelho novamente, alertando-o imediatamente.
Este ciclo se repete continuamente, construindo o software em pequenos incrementos e com uma base de testes robusta.
A terminologia correta da indústria é vital. Usamos testes de unidade para verificar componentes individuais isoladamente (uma função, um método de classe), testes de integração para verificar a interação entre diferentes partes do sistema (um controlador com um serviço, um serviço com um banco de dados), e testes end-to-end (E2E) para simular o fluxo completo de um usuário. Dentro dos testes, você encontrará termos como assertions (as verificações que seu teste faz, por exemplo, expect(resultado).to.equal(esperado)), mocks e stubs (objetos simulados que substituem dependências reais para isolar a parte sendo testada, permitindo controlar seu comportamento e verificar interações).
Casos de uso reais em produção para TDD incluem o desenvolvimento de microsserviços, onde a garantia de que cada serviço se comporta como um contrato é fundamental. Em sistemas com lógica de negócios complexa, o TDD ajuda a mapear e validar cada regra, prevenindo regressões. Projetos de longa duração se beneficiam enormemente, pois a suíte de testes atua como uma rede de segurança valiosa, permitindo que novas funcionalidades sejam adicionadas ou refatorações extensas sejam realizadas com confiança.
O TDD se integra com outras tecnologias de forma orgânica. É um pilar para a Integração Contínua (CI), onde cada push de código para o repositório dispara automaticamente a execução de todos os testes, garantindo que a base de código esteja sempre em um estado funcional. Isso, por sua vez, alimenta a Entrega Contínua (CD), possibilitando releases mais frequentes e com menos riscos.
Entre as vantagens do TDD, destacam-se:
- Qualidade do Código Superior: Os testes escritos primeiro forçam um design mais modular e coeso.
- Menos Bugs: Problemas são identificados e corrigidos mais cedo no ciclo de desenvolvimento.
- Documentação Viva: Os testes servem como exemplos claros de como o código deve ser usado e como se comporta.
- Confiança na Refatoração: A suíte de testes garante que mudanças não quebrem funcionalidades existentes.
- Velocidade a Longo Prazo: Embora pareça mais lento no início, a redução de bugs e a facilidade de manutenção aceleram o desenvolvimento no médio e longo prazo.
Contudo, existem desvantagens:
- Curva de Aprendizagem: Requer uma mudança de mentalidade e prática.
- Tempo Inicial Percebido: Pode parecer que demanda mais tempo no início do projeto.
- Testes Ruins: Testes mal escritos (muito acoplados, difíceis de manter) podem se tornar um fardo.
O balanço entre vantagens e desvantagens pende fortemente para o lado positivo, especialmente em equipes e projetos maduros que visam robustez e escalabilidade.
Implementação Prática
Vamos aplicar o TDD para construir a funcionalidade de criação de produtos em uma API Node.js com Express. Nossa API terá um serviço (lógica de negócio) e um controlador (interação com o Express).
Primeiro, vamos configurar nosso ambiente. Crie um novo diretório e inicialize um projeto Node.js:
mkdir tdd-product-api
cd tdd-product-api
npm init -y
Agora, instale as dependências essenciais: Express para o servidor, Mocha para o executor de testes e Chai para as asserções.
npm install express
npm install --save-dev mocha chai supertest
Usaremos também o supertest para facilitar os testes de integração HTTP.
Modifique seu package.json para incluir o script de teste:
{
"name": "tdd-product-api",
"version": "1.0.0",
"description": "",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"test": "mocha --recursive tests/*/.test.js --timeout 5000",
"dev": "nodemon src/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {
"chai": "^4.4.1",
"mocha": "^10.4.0",
"supertest": "^7.0.0",
"nodemon": "^3.1.0"
}
}
Instale o nodemon para desenvolvimento:
npm install --save-dev nodemon
Vamos estruturar nosso projeto para seguir as melhores práticas:
.
├── src/
│ ├── app.js
│ ├── server.js
│ ├── services/
│ │ └── productService.js
│ ├── controllers/
│ │ └── productController.js
│ └── routes/
│ └── productRoutes.js
└── tests/
├── unit/
│ └── productService.test.js
└── integration/
└── productIntegration.test.js
Passo 1: Red – Escrever o primeiro teste de unidade (que falha)
Vamos começar testando a lógica de negócio principal: criar um produto. Crie o arquivo tests/unit/productService.test.js:
// tests/unit/productService.test.js
const chai = require('chai');
const expect = chai.expect;
const productService = require('../../src/services/productService'); // O serviço ainda não existe!
describe('Product Service Unit Tests', () => {
// Limpa a "base de dados" em memória antes de cada teste para garantir isolamento
beforeEach(() => {
productService.clearProducts(); // Esta função ainda não existe!
});
it('deve criar um novo produto com um nome e preço válidos', () => {
// Cenário: Tentando criar um produto válido
const productData = { name: 'Teclado Mecânico', price: 150.00 };
const newProduct = productService.createProduct(productData); // O método ainda não existe!
// Asserção: Verifique se o produto foi criado corretamente
expect(newProduct).to.be.an('object');
expect(newProduct).to.have.property('id'); // Deve ter um ID gerado
expect(newProduct.name).to.equal('Teclado Mecânico');
expect(newProduct.price).to.equal(150.00);
expect(productService.getAllProducts()).to.have.lengthOf(1); // Deve haver um produto na lista
});
it('não deve criar um produto sem nome', () => {
// Cenário: Tentando criar um produto sem nome
const productData = { price: 100.00 };
// Esperamos que a criação de um produto inválido lance um erro
expect(() => productService.createProduct(productData)).to.throw('O nome do produto é obrigatório.');
expect(productService.getAllProducts()).to.have.lengthOf(0); // Nenhum produto deve ser adicionado
});
it('não deve criar um produto com preço inválido (negativo ou zero)', () => {
// Cenário: Tentando criar um produto com preço negativo
const productData = { name: 'Mouse Gamer', price: -50.00 };
expect(() => productService.createProduct(productData)).to.throw('O preço do produto deve ser um número positivo.');
expect(productService.getAllProducts()).to.have.lengthOf(0); // Nenhum produto deve ser adicionado
// Cenário: Tentando criar um produto com preço zero
const productDataZero = { name: 'Mouse Gamer', price: 0 };
expect(() => productService.createProduct(productDataZero)).to.throw('O preço do produto deve ser um número positivo.');
expect(productService.getAllProducts()).to.have.lengthOf(0); // Nenhum produto deve ser adicionado
});
});
Rode os testes:
npm test
Você verá um erro “Cannot find module ‘../../src/services/productService’”. Isso é VERMELHO! Ótimo, o teste está falhando como esperado.
Passo 2: Green – Escrever o código de produção mínimo
Crie o arquivo src/services/productService.js. Nosso “banco de dados” será um array em memória para simplificar a aula, tornando-o compatível com HostGator Plano M sem exigir configurações complexas de DB.
// src/services/productService.js
let products = []; // Nosso "banco de dados" em memória
let nextProductId = 1; // Para simular IDs únicos
/* @function createProduct
@description Cria um novo produto após validação.
@param {object} productData - Dados do produto (name, price).
@returns {object} O produto recém-criado.
@throws {Error} Se os dados do produto forem inválidos.
/
function createProduct(productData) {
// Validação de entrada robusta
if (!productData.name || typeof productData.name !== 'string' || productData.name.trim() === '') {
console.error('Erro de validação: Nome do produto é inválido.');
throw new Error('O nome do produto é obrigatório.');
}
if (typeof productData.price !== 'number' || productData.price <= 0) {
console.error('Erro de validação: Preço do produto é inválido.');
throw new Error('O preço do produto deve ser um número positivo.');
}
const newProduct = {
id: nextProductId++,
name: productData.name,
price: productData.price
};
products.push(newProduct);
console.log(Produto criado: ${JSON.stringify(newProduct)}); // Logging profissional básico
return newProduct;
}
/ @function getAllProducts
@description Retorna todos os produtos armazenados.
@returns {Array
/ @function clearProducts
@description Limpa a lista de produtos (para uso em testes).
/
function clearProducts() {
products = [];
nextProductId = 1;
console.log('Lista de produtos limpa.');
}
module.exports = {
createProduct,
getAllProducts,
clearProducts
};
Rode os testes novamente:
npm test
Todos os testes de unidade devem estar passando! Isso é VERDE!
Passo 3: Refactor – Melhorar o código (se necessário)
Neste ponto, o código está relativamente simples e claro. Para um exemplo mais complexo, poderíamos extrair a lógica de validação para um módulo separado, ou talvez utilizar uma biblioteca de validação como Joi ou Zod. No entanto, para a nossa aula, a validação inline é suficiente e clara. A refatoração aqui seria mais sobre organização e nomenclatura, que já estão razoáveis.
Implementação do Controlador e Rotas (TDD de Integração)
Agora vamos integrar este serviço em um controlador e rota. Primeiro, o teste de integração (Red):
Crie o arquivo tests/integration/productIntegration.test.js:
// tests/integration/productIntegration.test.js
const request = require('supertest');
const chai = require('chai');
const expect = chai.expect;
const app = require('../../src/app'); // Nossa aplicação Express ainda não existe!
const productService = require('../../src/services/productService');
describe('Product API Integration Tests', () => {
// Limpa os produtos antes de cada teste de integração
beforeEach(() => {
productService.clearProducts();
});
it('POST /products deve criar um produto com sucesso (status 201)', async () => {
// Cenário: Enviar dados válidos para criar um produto
const productData = { name: 'Monitor Ultrawide', price: 799.99 };
const res = await request(app) // Supertest faz a requisição HTTP
.post('/api/products') // Rota que ainda será criada
.send(productData)
.expect(201); // Espera-se status 201 Created
// Asserções: Verificar a resposta e o estado do sistema
expect(res.body).to.be.an('object');
expect(res.body).to.have.property('id');
expect(res.body.name).to.equal('Monitor Ultrawide');
expect(res.body.price).to.equal(799.99);
expect(productService.getAllProducts()).to.have.lengthOf(1);
});
it('POST /products deve retornar 400 se o nome estiver faltando', async () => {
// Cenário: Enviar dados de produto sem nome
const productData = { price: 250.00 };
const res = await request(app)
.post('/api/products')
.send(productData)
.expect(400); // Espera-se status 400 Bad Request
// Asserções: Verificar a mensagem de erro
expect(res.body).to.have.property('message', 'O nome do produto é obrigatório.');
expect(productService.getAllProducts()).to.have.lengthOf(0);
});
it('POST /products deve retornar 400 se o preço for inválido', async () => {
// Cenário: Enviar dados de produto com preço inválido
const productData = { name: 'Cadeira Gamer', price: -100.00 };
const res = await request(app)
.post('/api/products')
.send(productData)
.expect(400);
expect(res.body).to.have.property('message', 'O preço do produto deve ser um número positivo.');
expect(productService.getAllProducts()).to.have.lengthOf(0);
});
});
Rode os testes. Eles falharão com “Cannot find module ‘../../src/app’”. Isso é VERMELHO!
Green – Implementar Controlador, Rotas e App Express
Crie o arquivo src/controllers/productController.js:
// src/controllers/productController.js
const productService = require('../services/productService');
/* @function createProduct
@description Manipula a requisição HTTP para criar um novo produto.
@param {object} req - Objeto de requisição do Express.
@param {object} res - Objeto de resposta do Express.
/
async function createProduct(req, res) {
try {
const { name, price } = req.body; // Extrai dados do corpo da requisição
const newProduct = productService.createProduct({ name, price }); // Chama o serviço
res.status(201).json(newProduct); // Resposta de sucesso (201 Created)
console.log(Requisição POST /api/products bem-sucedida. Produto ID: ${newProduct.id});
} catch (error) {
// Error handling extraordinário: captura erros do serviço
console.error(Erro ao criar produto: ${error.message});
// Mapeia o erro para um status HTTP apropriado
if (error.message.includes('obrigatório') || error.message.includes('positivo')) {
return res.status(400).json({ message: error.message }); // Bad Request
}
res.status(500).json({ message: 'Erro interno do servidor ao criar produto.' }); // Erro genérico do servidor
}
}
// Futuras implementações:
// async function getProductById(req, res) { ... }
// async function getAllProducts(req, res) { ... }
module.exports = {
createProduct
};
Crie o arquivo src/routes/productRoutes.js:
// src/routes/productRoutes.js
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
// Define a rota POST para criar um novo produto
router.post('/', productController.createProduct);
// Futuras rotas:
// router.get('/:id', productController.getProductById);
// router.get('/', productController.getAllProducts);
module.exports = router;
Crie o arquivo src/app.js, que é a base da nossa aplicação Express:
// src/app.js
const express = require('express');
const productRoutes = require('./routes/productRoutes');
const app = express();
// Middleware para parsear JSON no corpo das requisições
app.use(express.json());
// Rota base para a API de produtos
app.use('/api/products', productRoutes);
// Middleware de tratamento de erros global (melhor prática enterprise)
app.use((err, req, res, next) => {
console.error(Erro inesperado: ${err.stack});
res.status(500).json({ message: 'Ocorreu um erro interno no servidor.' });
});
module.exports = app; // Exporta a instância do app para ser usada nos testes e no servidor
E finalmente, o arquivo src/server.js para iniciar o servidor:
// src/server.js
const app = require('./app');
// Configuração para HostGator Plano M ou ambiente de produção:
// Use a porta fornecida pelo ambiente (ex: process.env.PORT) ou a porta 3000 como fallback.
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(Servidor rodando na porta ${PORT});
console.log('API de produtos pronta para receber requisições.');
});
// Em ambientes de produção HostGator (com Passenger), o app.js geralmente é o entry point.
// Este server.js seria mais para testes locais ou outros ambientes.
// A compatibilidade com HostGator Plano M é garantida pela ausência de dependências complexas
// e uso de porta configurável.
Rode os testes novamente:
npm test
Todos os testes de integração devem estar passando! VERDE!
Refactor – Melhorar o código
Neste ponto, podemos considerar algumas melhorias:
- Logging: Integrar bibliotecas como
WinstonouMorganpara logging mais robusto e configurável, em vez deconsole.log. - Validação: Mover a validação de
productDatapara um middleware de validação ou usar uma biblioteca comoJoiouZodnas rotas. - Padronização de Erros: Criar classes de erro personalizadas (
ValidationError,NotFoundError) para um tratamento de erros mais descritivo e padronizado em toda a API. - Documentação: Gerar documentação da API com Swagger/OpenAPI.
Estas são melhorias que você faria com a segurança dos testes existentes.
Exercício Hands-On
Agora é sua vez de aplicar o TDD!
Desafio Prático
Utilizando o mesmo projeto e a abordagem TDD, implemente a funcionalidade de obter todos os produtos na sua API. Isso significa:
- Crie um novo teste de integração (Red) para o endpoint
GET /api/productsque espera retornar uma lista vazia se nenhum produto foi adicionado e uma lista com produtos se eles foram criados anteriormente. - Faça o teste passar (Green) implementando o método
getAllProductsnoproductController.jse adicionando a rotaGET /emproductRoutes.js. - Refatore (Refactor) se sentir necessidade.
Solução Detalhada Passo a Passo
1. Red – Escrever o teste de integração para GET /api/products
Adicione os seguintes testes ao final do arquivo tests/integration/productIntegration.test.js:
// ... (código anterior)
it('GET /products deve retornar uma lista vazia quando não há produtos', async () => {
// Cenário: Nenhuma produto adicionado ainda
const res = await request(app)
.get('/api/products') // Rota que ainda será criada
.expect(200); // Espera-se status 200 OK
// Asserções: A lista deve ser um array vazio
expect(res.body).to.be.an('array').that.is.empty;
expect(productService.getAllProducts()).to.have.lengthOf(0);
});
it('GET /products deve retornar todos os produtos existentes', async () => {
// Cenário: Criar alguns produtos primeiro
productService.createProduct({ name: 'Fone de Ouvido', price: 99.90 });
productService.createProduct({ name: 'Webcam HD', price: 120.00 });
const res = await request(app)
.get('/api/products')
.expect(200);
// Asserções: A lista deve conter os dois produtos criados
expect(res.body).to.be.an('array').that.has.lengthOf(2);
expect(res.body[0].name).to.equal('Fone de Ouvido');
expect(res.body[1].name).to.equal('Webcam HD');
});
// ... (fim do arquivo)
Rode npm test. Os novos testes devem falhar, provavelmente com um erro 404 (Not Found) ou 500 (Internal Server Error), pois a rota ainda não existe ou o controlador não está implementado. VERMELHO!
2. Green – Implementar a funcionalidade para fazer o teste passar
a. No src/controllers/productController.js, adicione a função getAllProducts:
// src/controllers/productController.js
const productService = require('../services/productService');
// ... (função createProduct anterior)
/* @function getAllProducts
@description Manipula a requisição HTTP para obter todos os produtos.
@param {object} req - Objeto de requisição do Express.
@param {object} res - Objeto de resposta do Express.
/
async function getAllProducts(req, res) {
try {
const products = productService.getAllProducts(); // Chama o serviço para obter os produtos
res.status(200).json(products); // Responde com status 200 OK e a lista de produtos
console.log('Requisição GET /api/products bem-sucedida. Total de produtos:', products.length);
} catch (error) {
console.error(Erro ao obter produtos: ${error.message});
res.status(500).json({ message: 'Erro interno do servidor ao buscar produtos.' });
}
}
module.exports = {
createProduct,
getAllProducts // Exporta a nova função
};
b. No src/routes/productRoutes.js, adicione a rota GET:
// src/routes/productRoutes.js
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
router.post('/', productController.createProduct);
router.get('/', productController.getAllProducts); // Adiciona a nova rota GET
module.exports = router;
Rode npm test novamente. Todos os testes, incluindo os novos, devem passar! VERDE!
3. Refactor – Melhorar o código
Neste caso, o código é simples e direto. Não há uma refatoração óbvia necessária de imediato. A estrutura de try/catch já garante um tratamento de erro básico. Você poderia considerar a padronização das mensagens de log e erro para toda a aplicação.
Como Testar e Validar o Resultado
Para validar seu resultado, execute npm test. Se todos os testes passarem, sua implementação está correta e funcional conforme as expectativas definidas nos testes.
Você também pode iniciar o servidor com npm start e testar manualmente usando ferramentas como Postman, Insomnia ou curl:
# Crie um produto (para ter algo para buscar)
curl -X POST -H "Content-Type: application/json" -d '{"name":"Smartwatch X","price":300.00}' http://localhost:3000/api/products
Busque todos os produtos
📚 Informações da Aula
Curso: API Completo - Node.js & Express
Tempo estimado: 25 minutos
Pré-requisitos: JavaScript básico
curl http://localhost:3000/api/products
A primeira requisição deve retornar o produto criado com status 201. A segunda deve retornar um array JSON contendo o Smartwatch X.
Troubleshooting dos Erros Mais Comuns
Cannot find module '...': Verifique os caminhos nos seusrequire(). Erros de caminho são muito comuns.- Testes falhando com 404 (Not Found) para rotas que você adicionou:
- Verifique se a rota está corretamente definida no
productRoutes.js(ex:router.get('/')). - Certifique-se de que
productRoutesestá sendo usado emapp.js(ex:app.use('/api/products', productRoutes)). - Confira se a URL no seu teste (
/api/products) corresponde à rota esperada.
- Verifique se a rota está corretamente definida no
- Testes falhando com 500 (Internal Server Error) inesperado:
- Examine os logs do seu servidor (ou do terminal onde você rodou
npm test) para ver a stack trace do erro. Isso indicará onde o erro original ocorreu. - Pode ser um erro na lógica do controlador ou do serviço, ou um problema com o tratamento de exceções.
- Examine os logs do seu servidor (ou do terminal onde você rodou
- Testes de asserção falhando (ex:
expected ... to equal ...):- Revise a lógica de retorno do seu controlador ou serviço. O que você está retornando é o que o teste espera?
- Verifique se os dados de entrada no teste estão corretos.
Próximos Passos Sugeridos
Para aprofundar seus conhecimentos em TDD e APIs:
- Adicione mais funcionalidades com TDD: Implemente
GET /api/products/:id(buscar por ID),PUT /api/products/:id(atualizar) eDELETE /api/products/:id(remover). - Aprofunde em Mocks e Stubs: Aprenda a usar ferramentas como Sinon.js para isolar ainda mais seus testes de unidade, simulando o comportamento de dependências externas (como um banco de dados real ou serviços externos).
- Autenticação e Autorização: Comece a explorar como implementar e testar autenticação e autorização em suas APIs usando TDD.
- Integração Contínua (CI): Configure seu projeto com uma ferramenta de CI (como GitHub Actions, GitLab CI ou Jenkins) para que seus testes rodem automaticamente a cada
push. - Bancos de Dados Reais: Substitua o array em memória por um banco de dados real (MongoDB com Mongoose, PostgreSQL com Sequelize/Prisma) e adapte seus testes. Lembre-se de ter um banco de dados de teste separado para evitar conflitos com dados de desenvolvimento.
- Melhore o Logging e Validação: Implemente
Winstonpara logging eJoiouZodpara validação de esquemas de entrada. Teste essas implementações também!
A prática contínua é a chave para dominar o TDD. Mantenha seus testes limpos, focados e rápidos, e você construirá APIs que são um verdadeiro deleite para desenvolver e manter.
🚀 Pronto para a próxima aula?
Continue sua jornada no desenvolvimento de APIs e domine Node.js & Express!