Node.js: Projeto API (Parte 3)
Última atualização: 2026-08-26
1. Finalização e Lançamento: O Terceiro Dia de Alice
No terceiro dia, Alice escreveu testes e a documentação da API, enquanto Bob configurou a implantação no Docker. “Só porque o código funciona não significa que esteja pronto para implantação”, disse Bob. “Os testes garantem a qualidade, a documentação garante a facilidade de manutenção e o Docker garante a consistência.”
- Jest + Supertest é a combinação padrão para testar APIs do Node.js
- O tratamento unificado de erros garante formatos consistentes de resposta da API
- O Swagger gera a documentação automaticamente; o código é a documentação.
- A conteinerização com o Docker garante a consistência entre os ambientes de desenvolvimento e de produção
- As verificações de integridade são uma configuração obrigatória para implantações em produção
2. Testes com o Jest e o Supertest
(1) Verificação da estrutura do arquivo
| Arquivo | Descrição |
|---|---|
tests/setup.js |
Configuração do ambiente de teste (conexão ao banco de dados de teste) |
tests/auth.test.js |
Teste do módulo de autenticação |
tests/tasks.test.js |
Testes do módulo de tarefas |
tests/helpers.js |
Funções auxiliares de teste (criação de usuários de teste, etc.) |
▶ Exemplo: Configuração do ambiente de teste
// tests/setup.js
process.env.JWT_SECRET = 'test-secret';
process.env.MONGO_URI = 'mongodb://localhost:27017/task-manager-test';
const mongoose = require('mongoose');
beforeAll(async () => await mongoose.connect(process.env.MONGO_URI));
afterAll(async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
});
▶ Exemplo: Testando o módulo de autenticação
const request = require('supertest');
const app = require('../src/app');
const User = require('../src/models/User');
describe('Auth API', () => {
beforeEach(async () => await User.deleteMany({}));
test('should register a new user', async () => {
const res = await request(app).post('/api/auth/register').send({
username: 'alice', email: 'alice@test.com', password: '123456'
});
expect(res.status).toBe(201);
expect(res.body.token).toBeDefined();
expect(res.body.user.username).toBe('alice');
});
test('should login existing user', async () => {
await request(app).post('/api/auth/register').send({
username: 'alice', email: 'alice@test.com', password: '123456'
});
const res = await request(app).post('/api/auth/login').send({
email: 'alice@test.com', password: '123456'
});
expect(res.status).toBe(200);
expect(res.body.token).toBeDefined();
});
test('should reject invalid credentials', async () => {
const res = await request(app).post('/api/auth/login').send({
email: 'noone@test.com', password: 'wrong'
});
expect(res.status).toBe(401);
});
});
▶ Exemplo: Testando o Módulo de Tarefas
const request = require('supertest');
const app = require('../src/app');
const Task = require('../src/models/Task');
const User = require('../src/models/User');
let token, userId;
beforeEach(async () => {
await User.deleteMany({});
await Task.deleteMany({});
const reg = await request(app).post('/api/auth/register').send({
username: 'bob', email: 'bob@test.com', password: '123456'
});
token = reg.body.token;
userId = reg.body.user.id;
});
test('should create a task', async () => {
const res = await request(app).post('/api/tasks').set('Authorization', `Bearer ${token}`).send({ title: 'Write tests' });
expect(res.status).toBe(201);
expect(res.body.title).toBe('Write tests');
});
test('should get tasks list', async () => {
await Task.create({ title: 'Task1', assignedTo: userId });
const res = await request(app).get('/api/tasks').set('Authorization', `Bearer ${token}`);
expect(res.status).toBe(200);
expect(res.body.tasks.length).toBe(1);
});
test('should deny access without token', async () => {
const res = await request(app).get('/api/tasks');
expect(res.status).toBe(401);
});
3. Encapsulamento unificado do tratamento de erros
▶ Exemplo: Classe de erro personalizada
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
module.exports = AppError;
▶ Exemplo: Middleware de tratamento global de erros
module.exports = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Internal Server Error';
res.status(statusCode).json({
status: statusCode >= 400 && statusCode < 500 ? 'fail' : 'error',
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
- Documentação da API Swagger
(1) Tags comuns de anotação do Swagger
| Tag | Descrição | Uso |
|---|---|---|
@openapi |
Definições de caminho e operação | Arquivos de roteamento |
@swagger |
Definição de componente (esquema) | Configuração da documentação |
@tags |
Grupos de API | Arquivos de roteamento |
@security |
Referência de métodos de autenticação | Pontos de extremidade que exigem autenticação |
@produces |
Formato da resposta | Arquivo de roteamento |
@parameters |
Parâmetros da solicitação | Arquivo de rota |
▶ Exemplo: Configuração do Swagger
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: { title: 'Task Manager API', version: '1.0.0', description: 'Team Task Management RESTful API' },
servers: [{ url: 'http://localhost:3000/api' }],
components: {
securitySchemes: {
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }
}
}
},
apis: ['./src/routes/*.js']
};
const specs = swaggerJsdoc(options);
module.exports = { swaggerUi, specs };
▶ Exemplo: Anotações do Swagger nas rotas
/**
* @openapi
* /auth/register:
* post:
* tags: [Auth]
* summary: User Registration
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [username, email, password]
* properties:
* username: { type: string }
* email: { type: string, format: email }
* password: { type: string, minLength: 6 }
* responses:
* 201:
* description: Registration Successful
* 400:
* description: Parameter error
*/
router.post('/register', async (req, res, next) => { /* ... */ });
4. Implantação do Docker
(1) Descrição do arquivo Docker
| Arquivo | Descrição |
|---|---|
Dockerfile |
Criando uma imagem de aplicativo Node.js |
docker-compose.yml |
Aplicativo de orquestração + contêiner do MongoDB |
.dockerignore |
Excluir arquivos desnecessários, como o node_modules |
(2) Lista de verificação para conclusão do projeto
| Item de verificação | Status |
|---|---|
| O projeto pode ser iniciado normalmente | ☐ |
| API de cadastro/login disponível | ☐ |
| Funções CRUD disponíveis | ☐ |
| Paginação e filtragem disponíveis | ☐ |
| O controle de acesso está funcionando normalmente | ☐ |
| Teste aprovado | ☐ |
| A documentação do Swagger está disponível | ☐ |
| Compilação do Docker bem-sucedida | ☐ |
| Ponto final da verificação de integridade: normal | ☐ |
| Tratamento unificado de erros | ☐ |
Exemplo: Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
▶ Exemplo: docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- MONGO_URI=mongodb://mongo:27017/task-manager
- JWT_SECRET=${JWT_SECRET}
depends_on:
- mongo
restart: unless-stopped
mongo:
image: mongo:7
volumes:
- mongo-data:/data/db
ports:
- "27017:27017"
volumes:
mongo-data:
▶ Exemplo: Endpoint de verificação de integridade
router.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime(), timestamp: new Date().toISOString() });
});
- Processo de CI/CD
graph LR
A[Code Push] --> B[Run Jest Test]
B -->|Through| C[Build Docker Image]
B -->|Failure| D[Notice to Developers]
C --> E[Push the image to Registry]
E --> F[Deploy to the server]
F --> G[Health Checkup]
G -->|Through| H[Deployment Complete]
G -->|Failure| I[Rollback to a Previous Version]
▶ Exemplo: Resumo dos comandos de inicialização do projeto
# Development Environment
npm run dev
# Run Test
npm test
# Docker Build
docker-compose up --build
# Production Deployment
docker-compose -f docker-compose.prod.yml up -d
5. Exemplo abrangente: Testes + Documentação + Configuração completa do Docker
// tests/tasks.test.js
const request = require('supertest');
const app = require('../src/app');
const Task = require('../src/models/Task');
const User = require('../src/models/User');
let token, userId;
beforeEach(async () => {
await User.deleteMany({});
await Task.deleteMany({});
const reg = await request(app).post('/api/auth/register')
.send({ username: 'testuser', email: 'test@test.com', password: '123456' });
token = reg.body.token;
userId = reg.body.user.id;
});
describe('Task API', () => {
test('POST /api/tasks - create task', async () => {
const res = await request(app).post('/api/tasks')
.set('Authorization', `Bearer ${token}`)
.send({ title: 'My Task', priority: 'high' });
expect(res.status).toBe(201);
});
test('GET /api/tasks - list with pagination', async () => {
for (let i = 0; i < 15; i++) {
await Task.create({ title: `Task ${i}`, assignedTo: userId });
}
const res = await request(app).get('/api/tasks?page=2&limit=5')
.set('Authorization', `Bearer ${token}`);
expect(res.status).toBe(200);
expect(res.body.tasks.length).toBe(5);
expect(res.body.page).toBe(2);
});
test('DELETE /api/tasks/:id - owner can delete', async () => {
const task = await Task.create({ title: 'To Delete', assignedTo: userId });
const res = await request(app).delete(`/api/tasks/${task._id}`)
.set('Authorization', `Bearer ${token}`);
expect(res.status).toBe(200);
});
});
// src/config/swagger.js
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const specs = swaggerJsdoc({
definition: {
openapi: '3.0.0',
info: { title: 'Task Manager API', version: '1.0.0' },
servers: [{ url: '/api' }],
components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer' } } }
},
apis: ['./src/routes/*.js']
});
module.exports = { swaggerUi, specs };
# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports: ["3000:3000"]
environment:
- MONGO_URI=mongodb://mongo:27017/task-manager
- JWT_SECRET=change_me_in_prod
depends_on: [mongo]
mongo:
image: mongo:7
volumes: [mongo-data:/data/db]
volumes:
mongo-data:
❓ Perguntas Frequentes
P: Como escolho entre o Jest e o Mocha? R: O Jest está pronto para uso assim que instalado, não requer configuração e inclui asserções e cobertura de código integradas; o Mocha é mais flexível, mas requer o chai ou o sinon. Para testes de API do Node.js, recomendamos o Jest + Supertest.
P: Como faço para testar endpoints que exigem autenticação no Supertest? R: Primeiro, chame o endpoint de login para obter um token e, em seguida, passe-o nas solicitações subsequentes usando .set('Authorization', 'Bearer ' + token).
P: Preciso escrever a documentação do Swagger manualmente? R: Você pode usar o
swagger-jsdocpara gerá-la automaticamente a partir dos comentários do JSDoc ou usar oswagger-ui-expresspara exibi-la. Os comentários servem como documentação, portanto, a manutenção é mínima.
P: Como posso otimizar o tamanho de uma imagem do Docker? R: Use a imagem base Alpine, compilações em múltiplas etapas (instale as dependências durante a fase de compilação e copie apenas os artefatos durante a execução) e um arquivo
.dockerignorepara excluir arquivos desnecessários.
P: Como faço para configurar um pipeline de CI/CD? R: Para projetos do GitHub, use o GitHub Actions: acionado por um push → instale as dependências → execute os testes → crie uma imagem do Docker → faça o push para o repositório → faça a implantação no servidor.
- P: Por que a documentação da API deve ser gerada automaticamente? R: A manutenção manual da documentação pode facilmente ficar desatualizada em relação ao código. As anotações do Swagger são escritas diretamente no código; portanto, ao alterar o código, a documentação é atualizada automaticamente, garantindo a consistência.
- P: Qual deve ser a cobertura dos testes? R: Para a lógica de negócios principal, recomendamos uma cobertura de pelo menos 80%, incluindo caminhos-chave como registro, login, CRUD e controle de acesso, bem como casos extremos.
- P: Quais são as vantagens da implantação com o Docker em relação à implantação em bare-metal? R: Consistência do ambiente (eliminando o problema de “isso funciona na minha máquina”), implantação rápida, isolamento de recursos, facilidade de integração com CI/CD e escalonamento horizontal.
- P: O que devo levar em consideração em um ambiente de produção? R: Use um segredo forte para JWT_SECRET, habilite a autenticação no MongoDB, habilite o HTTPS, restrinja as origens CORS, configure o limitador de taxa e configure a coleta de logs.
- P: Como faço para realizar uma verificação de integridade? R: Forneça o endpoint
/api/healthpara retornar o status do aplicativo e do banco de dados; o HEALTHCHECK do Docker ou o livenessProbe do Kubernetes acessa esse endpoint periodicamente. - P: O banco de dados de teste entrará em conflito com o banco de dados de produção? R: Os testes utilizam um banco de dados separado (como
task-manager-test), e os dados são limpos antes e depois de cada conjunto de testes, portanto, isso não afetará a produção.
📖 Resumo
- Conclusão e lançamento: conceitos básicos e uso do Alice no terceiro dia
- Conceitos básicos e uso do Jest + Supertest para testes
- Conceitos fundamentais e uso do encapsulamento do tratamento unificado de erros
- Conceitos básicos e uso da documentação da API Swagger
- Conceitos básicos e uso da implantação do Docker
- Conceitos fundamentais e melhores práticas para fluxos de trabalho de CI/CD
- Exemplo abrangente: conceitos básicos e uso de testes + documentação + configuração completa do Docker
📝 Exercícios
- Escreva pelo menos 5 casos de teste que abranjam a autenticação e as operações CRUD para tarefas e, em seguida, execute
npm testpara garantir que todos sejam aprovados. - Adicione anotações do Swagger a todas as rotas. Após iniciar o aplicativo, acesse
/api-docspara visualizar a documentação. - Crie os arquivos Dockerfile e docker-compose.yml e, em seguida, execute
docker-compose up --buildpara verificar a implantação. - Adicione o endpoint de verificação de integridade
/api/healthe configure a diretiva HEALTHCHECK no Docker. - Substitua todas as ocorrências de
throw new Error()pela classe personalizadaAppErrorpara garantir um formato consistente de resposta a erros.