Uma API REST moderna construída com Node.js, Fastify, TypeScript e PostgreSQL, utilizando Drizzle ORM para gerenciamento de banco de dados. Projeto completo com autenticação JWT, testes automatizados, filtros, paginação e deploy com Docker.
Este projeto é uma API completa para gerenciamento de cursos e usuários, desenvolvida como parte do desafio da Rocketseat. A aplicação demonstra boas práticas de desenvolvimento com TypeScript, validação de dados com Zod, autenticação JWT, testes automatizados com Vitest, documentação automática com Swagger/OpenAPI e gerenciamento de banco de dados com Drizzle ORM.
- Node.js - Runtime JavaScript
- TypeScript - Linguagem de programação tipada
- Fastify - Framework web rápido e eficiente
- Drizzle ORM - ORM moderno para TypeScript
- PostgreSQL - Banco de dados relacional
- Docker - Containerização
- Zod - Validação de esquemas
- Swagger/OpenAPI - Documentação da API
- Pino Pretty - Logs formatados
- JWT - Autenticação stateless
- Argon2 - Criptografia de senhas
- Vitest - Framework de testes
- Supertest - Testes de integração
- Faker.js - Geração de dados fictícios
- Node.js 18+
- Docker e Docker Compose
- npm ou yarn
- Clone o repositório
git clone <url-do-repositorio>
cd aulas- Instale as dependências
npm install- Configure as variáveis de ambiente
cp .env.example .env
# Edite o arquivo .env com suas configurações- Suba o banco de dados com Docker
docker compose up -d- Execute as migrações
npm run db:migrate- Popule o banco com dados de teste (opcional)
npm run db:seed- Inicie o servidor de desenvolvimento
npm run devO servidor estará rodando em http://localhost:3333
A documentação da API está disponível em:
- Swagger UI:
http://localhost:3333/docs(apenas em desenvolvimento) - Scalar API Reference: Interface mais moderna e bonita
id(UUID, Primary Key)name(Text, Not Null)email(Text, Not Null, Unique)password(Text, Not Null) - Criptografada com Argon2role(Enum: 'student' | 'manager', Default: 'student')
id(UUID, Primary Key)title(Text, Not Null, Unique)description(Text, Optional)
id(UUID, Primary Key)userId(UUID, Foreign Key -> users.id)courseId(UUID, Foreign Key -> courses.id)createdAt(Timestamp, Default: Now)- Índice único em (userId, courseId)
# Gerar SQL baseado no schema
npm run db:generate
# Executar migrações
npm run db:migrate
# Abrir o Drizzle Studio
npm run db:studio
# Popular banco com dados de teste
npm run db:seed├── src/
│ ├── database/
│ │ ├── client.ts # Configuração do cliente Drizzle
│ │ ├── schema.ts # Definição das tabelas
│ │ └── seed.ts # População do banco com dados fictícios
│ ├── routes/
│ │ ├── create-course.ts
│ │ ├── get-courses.ts
│ │ ├── get-course-by-id.ts
│ │ ├── login.ts
│ │ └── hooks/
│ ├── tests/
│ │ └── factories/ # Factories para testes
│ ├── utils/
│ │ └── get-authenticated-user-from-request.ts
│ ├── @types/
│ └── app.ts # Configuração da aplicação
├── drizzle/ # Arquivos de migração gerados
├── server.ts # Arquivo principal do servidor
├── drizzle.config.ts # Configuração do Drizzle
├── docker-compose.yml # Configuração do Docker
├── Dockerfile # Configuração para deploy
└── requests.http # Exemplos de requisições
npm run dev- Inicia o servidor em modo desenvolvimentonpm run db:generate- Gera arquivos de migraçãonpm run db:migrate- Executa migrações pendentesnpm run db:studio- Abre o Drizzle Studionpm run db:seed- Popula o banco com dados de testenpm test- Executa os testes automatizadosnpm run build- Constrói a imagem Docker
POST /login- Autenticar usuário e gerar JWT
POST /courses- Criar um novo curso (requer autenticação)GET /courses- Listar cursos com filtros e paginaçãoGET /courses/:id- Buscar curso por ID
# 1. Fazer login para obter o token JWT
curl -X POST http://localhost:3333/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password123"}'
# 2. Usar o token para criar um curso
curl -X POST http://localhost:3333/courses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"title": "Curso de Docker", "description": "Aprenda Docker do zero"}'
# 3. Listar cursos com filtros
curl "http://localhost:3333/courses?search=docker&page=1&perPage=10"
# 4. Buscar curso específico
curl http://localhost:3333/courses/{id}A API utiliza autenticação JWT (JSON Web Token) para proteger as rotas. O fluxo de autenticação funciona da seguinte forma:
- Login: Usuário envia email e senha
- Validação: Sistema verifica credenciais no banco
- Geração do Token: Se válido, gera um JWT com payload contendo:
sub: ID do usuáriorole: Papel do usuário (student/manager)iat: Timestamp de criação
- Uso: Token é enviado no header
Authorization: Bearer <token>
- Senhas são criptografadas com Argon2 (hash irreversível)
- JWT usa criptografia de chave simétrica (HS256)
- Tokens não armazenam informações sensíveis
- Qualquer alteração no token invalida a assinatura
search: Busca por título do cursoorderBy: Ordenação (title, createdAt)order: Direção da ordenação (asc, desc)
A API implementa Cursor-based Pagination para melhor performance:
page: Número da página (padrão: 1)perPage: Itens por página (padrão: 20, máximo: 100)
# Buscar cursos com filtros
GET /courses?search=docker&orderBy=title&order=asc&page=1&perPage=10
# Resposta
{
"courses": [...],
"pagination": {
"page": 1,
"perPage": 10,
"total": 25,
"totalPages": 3
}
}O projeto inclui uma suíte completa de testes usando Vitest e Supertest:
- E2E (End-to-End): Testam a aplicação de ponta a ponta
- Integração: Testam a comunicação entre componentes
- Unitários: Testam funções isoladas
# Executar todos os testes
npm test
# Executar com coverage
npm test -- --coverage
# Executar testes em modo watch
npm test -- --watchsrc/routes/
├── create-course.test.ts
├── get-courses.test.ts
├── get-course-by-id.test.ts
└── login.test.ts
O projeto utiliza factories para gerar dados de teste consistentes:
// src/tests/factories/make-course.ts
export function makeCourse(override: Partial<typeof courses.$inferInsert> = {}) {
return {
title: faker.lorem.words(4),
description: faker.lorem.sentence(),
...override,
}
}# Construir a imagem
docker build -t api-cursos .
# Executar o container
docker run -p 3333:3333 api-cursosFROM node:22-alpine AS builder
WORKDIR /app
COPY . ./
RUN npm ci
EXPOSE 3333
CMD ["sh", "-c", "npm run db:migrate && node src/server.ts"]DATABASE_URL=postgresql://user:password@host:5432/database
JWT_SECRET=your-super-secret-key
NODE_ENV=productiongraph TD
A[Cliente] --> B[Fastify Server]
B --> C[JWT Middleware]
C --> D[Zod Validation]
D --> E[Route Handler]
E --> F[Drizzle ORM]
F --> G[PostgreSQL]
G --> F
F --> E
E --> H[Response]
H --> A
I[Docker Compose] --> J[PostgreSQL Container]
J --> G
K[Vitest] --> L[Test Database]
L --> G
sequenceDiagram
participant Client as Cliente
participant Fastify as Fastify Server
participant Auth as Auth Handler
participant DB as PostgreSQL
participant JWT as JWT Service
Client->>Fastify: POST /login
Note over Client,Fastify: {email, password}
Fastify->>Auth: Executar Login
Auth->>DB: Buscar usuário por email
DB-->>Auth: Dados do usuário
Auth->>Auth: Verificar senha com Argon2
Auth->>JWT: Gerar token
JWT-->>Auth: JWT Token
Auth-->>Fastify: {token: "jwt..."}
Fastify-->>Client: 200 OK
sequenceDiagram
participant Client as Cliente
participant Fastify as Fastify Server
participant JWT as JWT Middleware
participant Zod as Zod Validation
participant Route as Route Handler
participant Drizzle as Drizzle ORM
participant DB as PostgreSQL
Client->>Fastify: POST /courses
Note over Client,Fastify: Authorization: Bearer <token>
Fastify->>JWT: Verificar token
JWT-->>Fastify: ✅ Token válido
Fastify->>Zod: Validar Body
Zod-->>Fastify: ✅ Validação OK
Fastify->>Route: Executar Handler
Route->>Drizzle: db.insert(courses)
Drizzle->>DB: INSERT INTO courses
DB-->>Drizzle: ID gerado
Drizzle-->>Route: Resultado
Route-->>Fastify: {courseId: "uuid"}
Fastify-->>Client: 201 Created
sequenceDiagram
participant Client as Cliente
participant Fastify as Fastify Server
participant Route as Route Handler
participant Drizzle as Drizzle ORM
participant DB as PostgreSQL
Client->>Fastify: GET /courses?search=docker&page=1
Fastify->>Route: Executar Handler
Route->>Drizzle: db.select().from(courses).where(like(title, '%docker%'))
Drizzle->>DB: SELECT * FROM courses WHERE title ILIKE '%docker%' LIMIT 20 OFFSET 0
DB-->>Drizzle: Cursos filtrados
Drizzle-->>Route: Array de cursos
Route-->>Fastify: {courses: [...], pagination: {...}}
Fastify-->>Client: 200 OK
- Configuração inicial do projeto
- Setup do TypeScript
- Configuração do Fastify
- Integração com Drizzle ORM
- Configuração do PostgreSQL com Docker
- Validação com Zod
- Documentação com Swagger/OpenAPI
- CRUD básico de cursos
- Filtros e paginação (Cursor-based)
- Autenticação JWT
- Criptografia de senhas com Argon2
- Testes automatizados com Vitest
- Factories para testes
- Deploy com Docker
- População de dados com Faker.js
- Soft Delete para cursos
- Refresh tokens
- Rate limiting
- Logs estruturados
- Métricas e observabilidade
- CI/CD com GitHub Actions
- Cache com Redis
- Upload de arquivos
O projeto usa um banco de dados separado para testes:
# Banco de teste é criado automaticamente
npm test
# Ver coverage
npm test -- --coverage// Exemplo de teste E2E
test('should create a course', async () => {
const courseData = makeCourse()
const response = await supertest(app)
.post('/courses')
.set('Authorization', `Bearer ${token}`)
.send(courseData)
expect(response.status).toBe(201)
expect(response.body).toHaveProperty('courseId')
})docker compose up -d dbdocker ps# Construir
docker build -t api-cursos .
# Executar
docker run -p 3333:3333 --env-file .env api-cursosA aplicação utiliza Pino Pretty para logs formatados e legíveis. Os logs incluem:
- Requisições HTTP
- Tempo de resposta
- Erros e warnings
- Informações de debug
- Logs de autenticação
- Senhas: Criptografadas com Argon2 (hash irreversível)
- JWT: Assinatura com chave secreta
- Validação: Todos os inputs validados com Zod
- SQL Injection: Prevenido pelo Drizzle ORM
- Rate Limiting: Implementado nas rotas sensíveis
- Faça um fork do projeto
- Crie uma branch para sua feature (
git checkout -b feature/AmazingFeature) - Commit suas mudanças (
git commit -m 'Add some AmazingFeature') - Push para a branch (
git push origin feature/AmazingFeature) - Abra um Pull Request
Este projeto está sob a licença ISC. Veja o arquivo LICENSE para mais detalhes.
Desenvolvido como parte do desafio da Rocketseat.
Nota: Este projeto demonstra uma API moderna com algumas das funcionalidades essenciais para produção, incluindo autenticação, testes, filtros, paginação e deploy.