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.”



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

JAVASCRIPT
// 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();
});
▶ Experimente

▶ Exemplo: Testando o módulo de autenticação

JAVASCRIPT
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);
  });
});
▶ Experimente

▶ Exemplo: Testando o Módulo de Tarefas

JAVASCRIPT
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);
});
▶ Experimente

3. Encapsulamento unificado do tratamento de erros

▶ Exemplo: Classe de erro personalizada

JAVASCRIPT
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

module.exports = AppError;
▶ Experimente

▶ Exemplo: Middleware de tratamento global de erros

JAVASCRIPT
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 })
  });
};
▶ Experimente
  1. 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

JAVASCRIPT
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 };
▶ Experimente

▶ Exemplo: Anotações do Swagger nas rotas

JAVASCRIPT
/**
 * @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) => { /* ... */ });
▶ Experimente

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

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

YAML
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

JAVASCRIPT
router.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime(), timestamp: new Date().toISOString() });
});
▶ Experimente
  1. Processo de CI/CD
100%
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

BASH
# 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

JAVASCRIPT
// 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);
  });
});
JAVASCRIPT
// 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
# 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"]
YAML
# 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-jsdoc para gerá-la automaticamente a partir dos comentários do JSDoc ou usar o swagger-ui-express para 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 .dockerignore para 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.


📖 Resumo

📝 Exercícios

  1. Escreva pelo menos 5 casos de teste que abranjam a autenticação e as operações CRUD para tarefas e, em seguida, execute npm test para garantir que todos sejam aprovados.
  2. Adicione anotações do Swagger a todas as rotas. Após iniciar o aplicativo, acesse /api-docs para visualizar a documentação.
  3. Crie os arquivos Dockerfile e docker-compose.yml e, em seguida, execute docker-compose up --build para verificar a implantação.
  4. Adicione o endpoint de verificação de integridade /api/health e configure a diretiva HEALTHCHECK no Docker.
  5. Substitua todas as ocorrências de throw new Error() pela classe personalizada AppError para garantir um formato consistente de resposta a erros.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%