Skip to content

Repository files navigation

Desafio Node.js - API de Cursos

API completa em Node.js + TypeScript usando Fastify, Drizzle ORM (PostgreSQL), autenticação JWT e validação com Zod. Inclui documentação Swagger, testes automatizados e cobertura de código.

Requisitos

  • Node.js 22+
  • Docker e Docker Compose
  • npm (ou outro gerenciador, mas o projeto usa package-lock.json)

Tecnologias e Bibliotecas

Core Framework

  • Fastify 5 - Framework web rápido e eficiente para Node.js
  • TypeScript - Tipagem estática para JavaScript
  • fastify-type-provider-zod - Integração entre Fastify e Zod para validação tipada

Banco de Dados

  • Drizzle ORM - ORM moderno e type-safe para TypeScript
  • PostgreSQL - Banco de dados relacional
  • pg - Driver PostgreSQL para Node.js

Validação e Schemas

  • Zod - Biblioteca de validação e parsing de schemas TypeScript-first

Autenticação e Segurança

  • jsonwebtoken - Implementação de JSON Web Tokens para autenticação
  • argon2 - Algoritmo de hash seguro para senhas

Documentação

  • @fastify/swagger - Geração automática de documentação OpenAPI/Swagger
  • @fastify/swagger-ui - Interface web para documentação da API

Testes

  • Vitest - Framework de testes rápido e moderno
  • @vitest/coverage-v8 - Cobertura de código usando V8
  • Supertest - Biblioteca para testes de APIs HTTP
  • @faker-js/faker - Geração de dados fake para testes e seed

Utilitários

  • pino-pretty - Formatação de logs legíveis
  • dotenv-cli - Carregamento de variáveis de ambiente

Configuração

  1. Clone o repositório e acesse a pasta do projeto.
  2. Instale as dependências:
npm install
  1. Suba os bancos PostgreSQL (desenvolvimento e teste) com Docker:
docker compose up -d
  1. Rode as migrações (Drizzle):
npm run db:migrate
  1. (Opcional) Popule o banco com dados de exemplo:
npm run db:seed

(Opcional) Para inspecionar o schema/estado com o Drizzle Studio:

npm run db:studio

Executando o servidor

npm run dev
  • Porta padrão: http://localhost:3333
  • Logs legíveis habilitados
  • Documentação da API (em dev): http://localhost:3333/docs

Endpoints

Base URL: http://localhost:3333

Autenticação

  • POST /sessions
    • Login do usuário
    • Body (JSON):
      { "email": "user@example.com", "password": "12345aA@" }
    • Respostas:
      • 200: { "token": "jwt-token" }
      • 400: { "message": "Credenciais inválidas." }

Cursos (Requer Autenticação)

  • POST /courses 🔒 Manager only

    • Cria um curso
    • Headers: Authorization: jwt-token
    • Body (JSON):
      { "title": "Curso de Docker" }
    • Respostas:
      • 201: { "courseId": "<uuid>" }
  • GET /courses 🔒 Authenticated users

    • Lista todos os cursos com paginação e busca
    • Headers: Authorization: jwt-token
    • Query params: search, orderBy, page
    • 200: { "courses": [{ "id": "<uuid>", "title": "...", "enrollments": 5 }], "total": 10 }
  • GET /courses/:id 🔒 Authenticated users

    • Busca um curso pelo ID
    • Headers: Authorization: jwt-token
    • Parâmetros: id (UUID)
    • Respostas:
      • 200: { "course": { "id": "<uuid>", "title": "...", "description": "... | null" } }
      • 404: vazio
  • PATCH /courses/:id 🔒 Manager only

    • Atualiza um curso existente
    • Headers: Authorization: jwt-token
    • Parâmetros: id (UUID)
    • Body (JSON):
      { "title": "Novo título", "description": "Nova descrição" }
    • Respostas:
      • 200: { "course": { "id": "<uuid>", "title": "...", "description": "..." } }
      • 404: { "error": "Curso não encontrado." }
  • DELETE /courses/:id 🔒 Manager only

    • Remove um curso existente
    • Headers: Authorization: jwt-token
    • Parâmetros: id (UUID)
    • Respostas:
      • 200: { "deleted": "Curso deletado com sucesso." }
      • 404: { "error": "Curso não encontrado." }

Há um arquivo request.http com exemplos prontos (compatível com extensões de REST Client).

FireShot Capture 006 - Swagger UI -  localhost

Modelos (Schema)

Tabelas principais definidas em src/database/schema.ts:

  • users

    • id (uuid, pk, default random)
    • name (text, obrigatório)
    • email (text, único, obrigatório)
    • password (text, obrigatório, hash argon2)
    • role (enum: "student" | "manager", default "student")
  • courses

    • id (uuid, pk, default random)
    • title (text, único, obrigatório)
    • description (text, opcional)
  • enrollments

    • id (uuid, pk, default random)
    • userId (uuid, fk para users.id)
    • courseId (uuid, fk para courses.id)
    • createdAt (timestamp, default now)

Cobertura de Testes

image

Cobertura geral: 97.2% das linhas cobertas

Fluxo Principal (Mermaid)

sequenceDiagram
    participant C as Client
    participant S as Fastify Server
    participant JWT as JWT Middleware
    participant V as Zod Validator
    participant DB as Drizzle + PostgreSQL

    %% Login Flow
    C->>S: POST /sessions {email, password}
    S->>V: Validar body
    V-->>S: OK ou Erro 400
    alt válido
        S->>DB: SELECT user WHERE email=...
        DB-->>S: user data
        S->>S: Verificar senha (argon2)
        alt senha correta
            S->>S: Gerar JWT token
            S-->>C: 200 {token}
        else senha incorreta
            S-->>C: 400 {message: "Credenciais inválidas"}
        end
    else inválido
        S-->>C: 400
    end

    %% Manager-only Course Operations
    C->>S: POST /courses {title} + Authorization header
    S->>JWT: Verificar token JWT
    JWT-->>S: OK (user data) ou 401
    S->>S: Verificar role (manager)
    alt autorizado
        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
    else não autorizado
        S-->>C: 401
    end

    C->>S: GET /courses + Authorization header
    S->>JWT: Verificar token JWT
    JWT-->>S: OK (user data) ou 401
    S->>S: Verificar role (manager)
    alt autorizado
        S->>DB: SELECT courses com enrollments count
        DB-->>S: lista paginada
        S-->>C: 200 {courses: [...], total}
    else não autorizado
        S-->>C: 401
    end

    %% Authenticated User Operations
    C->>S: GET /courses/:id + Authorization header
    S->>JWT: Verificar token JWT
    JWT-->>S: OK (user data) ou 401
    alt autenticado
        S->>V: Validar param id (uuid)
        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
    else não autenticado
        S-->>C: 401
    end

    %% Manager-only Update/Delete Operations
    C->>S: PATCH /courses/:id {title, description} + Authorization
    S->>JWT: Verificar token JWT
    JWT-->>S: OK (user data) ou 401
    S->>S: Verificar role (manager)
    S->>V: Validar param id (uuid) e body
    V-->>S: OK ou Erro 400
    alt válido e encontrado
        S->>DB: UPDATE courses SET ... WHERE id=...
        DB-->>S: updated course
        S-->>C: 200 {course}
    else inválido
        S-->>C: 400
    else não encontrado
        S-->>C: 404
    end

    C->>S: DELETE /courses/:id + Authorization
    S->>JWT: Verificar token JWT
    JWT-->>S: OK (user data) ou 401
    S->>S: Verificar role (manager)
    S->>V: Validar param id (uuid)
    V-->>S: OK ou Erro 400
    alt válido e encontrado
        S->>DB: DELETE FROM courses WHERE id=...
        DB-->>S: OK
        S-->>C: 200 {deleted: "..."}
    else inválido
        S-->>C: 400
    else não encontrado
        S-->>C: 404
    end
Loading

Scripts

  • npm run dev: inicia o servidor com reload e carrega variáveis de .env
  • npm run db:generate: gera artefatos do Drizzle a partir do schema
  • npm run db:migrate: aplica migrações no banco
  • npm run db:studio: abre o Drizzle Studio
  • npm run db:seed: popula o banco com dados de exemplo usando Faker
  • npm run test: executa testes E2E com cobertura de código
  • npm run pretest: executa migrações no banco de teste antes dos testes

Testes E2E (End-to-End)

O projeto utiliza Vitest como framework de testes E2E com as seguintes características:

  • Testes end-to-end para todas as rotas da API
  • Cobertura de código com @vitest/coverage-v8
  • Dados fake gerados com @faker-js/faker
  • Banco de dados separado para testes (courseApiTest na porta 5433)
  • Factory functions para criação de usuários e cursos de teste
  • Testes de autenticação e autorização completos
  • Simulação de requisições HTTP reais com Supertest

Para executar os testes:

npm run test

Dicas e Solução de Problemas

  • Conexão recusada ao Postgres: confirme docker compose up -d e que as portas 5432 e 5433 não estão em uso.
  • Variável DATABASE_URL ausente: verifique seu .env. O Drizzle exige essa variável para db:generate, db:migrate e db:studio.
  • Erro de JWT: verifique se JWT_SECRET está definido no .env.
  • Testes falhando: certifique-se de que o banco de teste está rodando na porta 5433.

Licença

ISC (ver package.json).

About

API Node 22 c/ Fastify 5 e Drizzle ORM de gerenciamento de cursos com sistema de autenticação baseado em roles (estudante/manager), incluindo: CRUD com validação de dados, autenticação JWT com diferentes níveis de acesso, sistema de matrículas com relacionamentos entre usuários e cursos, documentação com Swagger e cobertura de testes E2E c/ Vitest

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages