API simples em Node.js + TypeScript usando Fastify, Drizzle ORM (SQLite) e Zod. Inclui documentação Swagger/Scalar em ambiente de desenvolvimento.
- Node.js 20.12.2+ (recomendado usar .nvmrc)
- npm (ou outro gerenciador, mas o projeto usa
package-lock.json)
- Fastify 5 - Framework web rápido e eficiente
- TypeScript - Superset do JavaScript com tipagem estática
- Drizzle ORM + SQLite - ORM moderno e type-safe com banco embarcado
- Zod - Validação de dados
- Swagger/OpenAPI + Scalar API Reference (em
/docsquandoNODE_ENV=development) - tsx - Executor TypeScript para desenvolvimento
- pino-pretty - Logger formatado para desenvolvimento
- Clone o repositório e acesse a pasta do projeto.
- Instale as dependências:
npm install- Crie um arquivo
.envna raiz com:
# Ativa docs em /docs
NODE_ENV=development- Configure o banco de dados SQLite:
# Opção 1: Setup completo (gerar + aplicar migrações)
npm run db:setup
# Opção 2: Passo a passo
npm run migrate:generate # Gera arquivo SQL de migração
npm run migrate # Aplica migrações no banco- (Opcional) Popular banco de dados com dados de exemplo:
# Seed completo (usuários + cursos + matrículas)
# Padrão: 5 usuários, 20 cursos, 3 matrículas por usuário
npm run db:seed
# Seed completo com quantidades customizadas
npm run db:seed -- --users=10 --courses=8 --enrollments=4
# Seeds individuais (caso prefira popular separadamente)
npm run db:seed-users # Padrão: 2 usuários
npm run db:seed-users 10 # Criar 10 usuários
npm run db:seed-courses # Padrão: todos os 20 cursos
npm run db:seed-courses 5 # Criar apenas 5 cursos- (Opcional) Para inspecionar o schema/estado com o Drizzle Studio:
npm run drizzle:studionpm run dev- Porta padrão:
http://localhost:3333 - Logs legíveis habilitados
- Documentação da API (em dev):
http://localhost:3333/docs
Base URL: http://localhost:3333
-
POST
/courses- Cria um curso único ou múltiplos cursos
- Body (JSON):
ou array para múltiplos:
{ "title": "Curso de Docker", "description": "Aprenda Docker" }[ { "title": "Curso A", "description": "Desc A" }, { "title": "Curso B" } ] - Respostas: 201 (sucesso), 500 (erro)
-
GET
/courses- Lista todos os cursos com paginação, busca e ordenação
- Query params:
page,limit,search,orderBy - 200:
{ "courses": [...], "currentPage": 1, "perPage": 10, "totalItems": 20, "totalPages": 2 }
-
GET
/courses/:id- Busca um curso pelo ID
- Respostas: 200 (encontrado), 404 (não encontrado)
-
POST
/users- Cria um usuário único ou múltiplos usuários
- Body (JSON):
ou array para múltiplos usuários
{ "first_name": "João", "last_name": "Silva", "email": "joao@email.com" } - Respostas: 201 (sucesso), 500 (erro)
-
GET
/users- Lista todos os usuários com paginação, busca e ordenação
- Query params:
page,limit,search,orderBy - 200:
{ "users": [...], "currentPage": 1, "perPage": 10, "totalItems": 5, "totalPages": 1 }
-
GET
/users/:id- Busca um usuário pelo ID
- Respostas: 200 (encontrado), 404 (não encontrado)
-
POST
/enrollments- Cria uma matrícula única ou múltiplas matrículas
- Body (JSON):
ou array para múltiplas matrículas
{ "user_id": 1, "course_id": 1 } - Respostas: 201 (sucesso), 500 (erro)
-
GET
/enrollments- Lista todas as matrículas com paginação e filtros
- Query params:
page,limit,user_id,course_id - 200:
{ "enrollments": [...], "currentPage": 1, "perPage": 10, "totalItems": 15, "totalPages": 2 } - Retorna dados enriquecidos com nome do usuário e título do curso
-
GET
/enrollments/:user_id/:course_id- Busca uma matrícula específica por user_id e course_id
- Respostas: 200 (encontrado), 404 (não encontrado)
Há arquivos .http na pasta src/requests/ com exemplos prontos (compatível com extensões de REST Client):
src/requests/courses/courses.http- Requisições de cursossrc/requests/users/users.http- Requisições de usuáriossrc/requests/enrollments/enrollments.http- Requisições de matrículas
api-node/
├── docs/ # Documentação técnica (local)
│ ├── README.md # Visão geral da documentação
│ ├── instrucoes.md # Instruções de setup
│ ├── migracoes-drizzle.md # Guia de migrações
│ └── drizzle-studio-setup.md # Configuração do Studio
├── drizzle/ # Migrações do banco de dados
├── src/
│ ├── database/ # Schema e cliente do banco
│ │ ├── schema.ts # Definição das tabelas
│ │ ├── client.ts # Cliente SQLite
│ │ ├── dev.db # Arquivo do banco SQLite
│ │ └── seeds/ # Scripts para popular o banco
│ │ ├── index.ts # Seed completo (users + courses + enrollments)
│ │ ├── seed-users.ts # Seed apenas de usuários
│ │ └── seed-courses.ts # Seed apenas de cursos
│ ├── requests/ # Arquivos de requisições HTTP (REST Client)
│ │ ├── courses/
│ │ │ └── courses.http # Exemplos de requisições de cursos
│ │ ├── users/
│ │ │ └── users.http # Exemplos de requisições de usuários
│ │ └── enrollments/
│ │ └── enrollments.http # Exemplos de requisições de matrículas
│ ├── routes/ # Rotas da API
│ │ ├── courses/ # Rotas de cursos (get, create, get-by-id)
│ │ ├── users/ # Rotas de usuários (get, create, get-by-id)
│ │ └── enrollments/ # Rotas de matrículas (get, create, get-by-ids)
│ └── scripts/ # Scripts utilitários
│ ├── database/ # Scripts de gerenciamento do banco
│ │ ├── index.js # Exportações
│ │ ├── apply-migration.js # Aplicar migrações
│ │ ├── check-db.js # Verificar status do banco
│ │ └── reset-table.js # Resetar tabelas específicas
│ ├── git/ # Scripts de automação Git
│ │ ├── index.js # Exportações
│ │ ├── cleanup.js # Limpar processos
│ │ ├── deploy.js # Deploy automatizado
│ │ ├── push-dev.js # Push para dev
│ │ └── status.js # Git status formatado
│ └── README.md # Documentação dos scripts
├── .claude/ # Configurações Claude (local)
├── server.ts # Servidor principal
├── CLAUDE.md # Documentação Claude (local)
├── package.json # Configurações e dependências
├── tsconfig.json # Configuração TypeScript
├── drizzle.config.ts # Configuração Drizzle Kit
└── README.md # Este arquivo
Nota: Os diretórios
docs/,.claude/e o arquivoCLAUDE.mdsão locais e não são versionados no Git.
Tabelas principais definidas em src/database/schema.ts:
coursesid(integer, pk, auto-increment)title(text, único, obrigatório)description(text, opcional)created_at(timestamp, obrigatório)updated_at(timestamp, obrigatório)
usersid(integer, pk, auto-increment)first_name(text, obrigatório)last_name(text, obrigatório)email(text, único, obrigatório)created_at(timestamp, obrigatório)updated_at(timestamp, obrigatório)
enrollmentsuser_id(integer, fk → users.id, obrigatório)course_id(integer, fk → courses.id, obrigatório)created_at(timestamp, obrigatório)updated_at(timestamp, obrigatório)
npm run dev: inicia o servidor com reload e carrega variáveis de.envnpm run drizzle:studio: abre o Drizzle Studio
npm run migrate:generate: gera artefatos do Drizzle a partir do schemanpm run migrate: aplica migrações no banconpm run db:setup: configura o banco de dados (gera e aplica migrações)npm run db:reset: DELETA o banco SQLite e recria do zeronpm run db:check: verifica o status do banco SQLite
npm run db:seed: executa seed completo (usuários + cursos + matrículas)- Padrão: 5 usuários, 20 cursos, 3 matrículas/usuário
- Com parâmetros:
npm run db:seed -- --users=10 --courses=8 --enrollments=4 - Algoritmo de matrículas: Cada usuário é matriculado em N cursos aleatórios sem repetição
- Exemplo: 3 usuários, 5 cursos, 2 matrículas/usuário = 6 matrículas totais
- User 1 → sorteados: Course A, Course D
- User 2 → sorteados: Course B, Course E
- User 3 → sorteados: Course C, Course A
npm run db:seed-users [quantidade]: cria apenas usuários (padrão: 2)npm run db:seed-courses [limite]: cria apenas cursos (padrão: todos os 20)
Importante: Estes comandos apagam todos os registros da tabela e resetam os IDs autoincrementais para 1.
npm run db:reset-table [nome]: reseta tabela específica (users, courses ou enrollments)npm run db:reset-users: limpa apenas a tabelausersnpm run db:reset-courses: limpa apenas a tabelacoursesnpm run db:reset-enrollments: limpa apenas a tabelaenrollmentsnpm run db:reset-all-tables: limpa TODAS as tabelas (mantém estrutura do banco)
Exemplo de uso:
# Cenário: Você tem cursos com IDs 5, 6, 7 e quer recomeçar do ID 1
npm run db:reset-courses # Limpa tabela courses
npm run db:seed-courses 3 # Cria 3 cursos com IDs 1, 2, 3sequenceDiagram
participant C as Client
participant S as Fastify Server
participant V as Zod Validator
participant DB as Drizzle + SQLite
C->>S: POST /courses {title}
S->>V: Validar body
V-->>S: OK ou Erro 400
alt válido
S->>DB: INSERT INTO courses (title)
DB-->>S: {id}
S-->>C: 201 {courseId}
else inválido
S-->>C: 400
end
C->>S: GET /courses
S->>DB: SELECT id,title FROM courses
DB-->>S: lista
S-->>C: 200 {result: [...]}
C->>S: GET /courses/:id
S->>V: Validar param id (number)
V-->>S: OK ou Erro 400
alt encontrado
S->>DB: SELECT * FROM courses WHERE id=...
DB-->>S: course
S-->>C: 200 {course}
else não encontrado
S-->>C: 404
end
Solução:
# 1. Verificar status do banco
npm run db:check
# 2. Se o banco estiver vazio ou corrompido, execute:
npm run db:reset
# 3. Popular com dados de exemplo
npm run db:seed -- --users=5 --courses=10 --enrollments=3Solução:
# 1. Regenerar migrações
npm run migrate:generate
# 2. Aplicar migrações
npm run migrate
# 3. Verificar se funcionou
npm run db:checkSolução:
# Resetar uma tabela específica (apaga dados e reseta IDs)
npm run db:reset-courses
npm run db:reset-users
# Resetar TODAS as tabelas (mantém estrutura do banco)
npm run db:reset-all-tables
# Popular novamente com dados limpos
npm run db:seed -- --users=5 --courses=10 --enrollments=3npm run db:reset: DELETA o arquivo do banco e recria tudo do zero (estrutura + dados)npm run db:reset-table [nome]: Limpa apenas UMA tabela, mantém estrutura e reseta IDs para começar do 1npm run db:reset-all-tables: Limpa TODAS as tabelas, mas mantém a estrutura do banco
- Arquivo SQLite não encontrado: confirme que
npm run db:setupfoi executado e que o arquivosrc/database/dev.dbexiste. - Variável
NODE_ENVausente: verifique seu.env. A documentação em/docssó aparece quandoNODE_ENV=development. - Docs não aparecem em
/docs: garantaNODE_ENV=developmentno.enve reinicie o servidor. - Banco corrompido: execute
npm run db:resetpara resetar o banco SQLite. - Erro ao popular dados: certifique-se de que as migrações foram aplicadas antes de rodar os seeds.
ISC (ver package.json).