Uma API RESTful desenvolvida com Domain-Driven Design (DDD) e Clean Architecture, estruturada de forma limpa, modular e escalável para o gerenciamento de usuários, tópicos e posts em um fórum.
- 📖 Visão Geral
- 🛠️ Tecnologias Utilizadas
- ⚙️ Funcionalidades
- 📚 Conceitos Aplicados
- 🏗️ Estrutura da Aplicação
- 🚀 Como Executar o Projeto
- 📌 Tabela de Endpoints
- 📡 Endpoints Detalhados
- 🧪 Testes
- 🤝 Contribuições
- ⭐ Apoie este Projeto
- 📞 Contato
Este projeto é uma API RESTful robusta e altamente modular para gerenciar tópicos, posts e usuários em um fórum. Construída utilizando NestJS e baseada nos princípios da Clean Architecture, a aplicação promove uma divisão clara de responsabilidades entre suas camadas, garantindo alta coesão e baixo acoplamento.
A arquitetura foi pensada para ser escalável, segura e extensível, incorporando práticas de Desenvolvimento Orientado a Testes (TDD) e aplicando rigorosamente os princípios do SOLID. Além disso, a utilização de Padrões de Projeto estratégicos proporciona soluções elegantes para problemas recorrentes de software, elevando a qualidade, a manutenibilidade e a performance do sistema.
O projeto também integra tecnologias modernas como Prisma ORM, PostgreSQL, Redis e armazenamento em nuvem via S3/R2, proporcionando alta eficiência no gerenciamento de dados e arquivos.
- 🟢 Node.js: Plataforma para execução do JavaScript no servidor.
- 🔧 NestJS: Framework para construir aplicações Node.js eficientes e escaláveis.
- 🟦 TypeScript: Superset de JavaScript que adiciona tipagem estática.
- 🗄️ Prisma: ORM moderno e robusto para banco de dados.
- 🐘 PostgreSQL: Banco de dados relacional utilizado para armazenar informações.
- 🔴 Redis: Sistema de cache em memória e armazenamento de dados temporários.
- 🐳 Docker: Containerização da aplicação.
- ☁️ S3 da AWS via R2 da Cloudflare: Armazenamento de arquivos na nuvem.
- 🔒 Bcrypt: Biblioteca para hash de senhas de forma segura.
- 📅 DayJS: Biblioteca para manipulação de datas e horas.
- 💎 Zod: Biblioteca para validação de dados.
- 🔐 Passport-JWT: Middleware de autenticação baseado em JWT.
- 🏗️ Reflect-Metadata: Biblioteca que adiciona suporte a metadados em TypeScript.
- ⚡ RxJS: Biblioteca para programação reativa com observables.
- 🧩 Faker: Biblioteca para gerar dados falsos para testes.
- 📦 Multer: Middleware para upload de arquivos.
- 🧪 Vitest: Framework de testes para TypeScript e JavaScript.
- 🕷️ Supertest: Framework de testes para APIs HTTP.
- ⚙️ Dotenv: Carrega variáveis de ambiente.
- 🛠️ ESLint: Linter para garantir a qualidade do código.
- 🔐 Autenticação segura de usuários: Registro, login e proteção de rotas privadas com tokens JWT.
- 📝 Criação e gestão de tópicos e posts: Usuários podem criar, editar e excluir seus próprios tópicos e posts.
- 💬 Sistema de comentários: Comentários podem ser adicionados tanto a tópicos quanto a respostas.
- 🏆 Escolha da melhor resposta: O autor de um tópico pode marcar uma resposta como a melhor solução.
- 🔍 Busca eficiente e listagem de tópicos: Permite consultar tópicos recentes, respostas e comentários.
- 🗑️ Gerenciamento de conteúdo: Permite a exclusão de posts, comentários e respostas específicas.
- ☁️ Upload de arquivos: Integração com serviços de armazenamento na nuvem para envio de arquivos.
- ⚡ Cache otimizado: Uso de Redis para acelerar a recuperação de informações e reduzir a carga no banco de dados.
- 🧪 Testes automatizados: Cobertura abrangente com testes unitários e de integração.
- 🛠️ Estrutura escalável: Facilitada por princípios de Clean Architecture e SOLID para crescimento sustentável do sistema.
- 🧩 Domain-Driven Design (DDD): Organização do código em domínios ricos com foco na modelagem do negócio.
- 🛡️ Clean Architecture: Separação clara de responsabilidades entre domínio, aplicação, infraestrutura e interface.
- 🧱 Entidades e Value Objects: Modelagem orientada ao domínio com foco na integridade dos dados.
- 📦 Camada Core Compartilhada: Reutilização de entidades e contratos genéricos entre domínios.
- 📂 Repositórios como Contratos (Interfaces): Isolamento entre lógica de domínio e persistência.
- 🧪 Test-Driven Development (TDD): Estrutura de testes com repositórios in-memory e fábricas de entidades.
- 🏭 Factory Pattern para Testes: Criação facilitada de entidades e casos de uso em testes.
- 🔗 Aggregate Root: Entidades raiz que controlam a consistência dos agregados.
- 📜 Watched List: Rastreio de alterações em coleções internas de entidades.
- 🌐 Domain Events: Comunicação entre domínios desacoplada via eventos explícitos.
- 📣 Event Subscribers: Reações automatizadas a eventos de domínio, como envio de notificações.
↔️ Either (Programação Funcional): Encapsulamento explícito de erros e resultados esperados.- ⚙️ Use Cases Desacoplados: Casos de uso desacoplados de infraestrutura e testáveis isoladamente.
- 💡 Presenters: Separação entre modelos de domínio e estruturas de resposta HTTP.
- 🔐 Autenticação Modular: Implementações desacopladas de criptografia e autenticação.
- 🛠️ Pipes Customizados: Validação e transformação de dados com lógica própria.
- 🧠 Mapper Pattern: Conversão entre modelos ORM e entidades de domínio.
A arquitetura do projeto segue os princípios da Clean Architecture, com separação clara de responsabilidades entre camadas de domínio, infraestrutura e aplicação. Abaixo está a descrição das principais pastas e seus propósitos:
nest-clean-api/
├── src/
│ ├── core/ # Camada genérica e reutilizável entre os domínios
│ │ ├── entities/ # Entidades base como AggregateRoot e WatchedList
│ │ ├── errors/ # Exceções e erros comuns de domínio
│ │ ├── events/ # Eventos genéricos utilizados em múltiplos contextos
│ │ ├── repositories/ # Interfaces genéricas de repositórios
│ │ ├── types/ # Tipos e utilitários auxiliares
│ ├── domain/ # Camadas específicas por contexto de domínio
│ │ ├── forum/ # Domínio do fórum (posts, tópicos, comentários)
│ │ │ ├── application/ # Casos de uso, contratos e interfaces
│ │ │ │ ├── cryptography/ # Interfaces de criptografia (ex: encrypter, hasher)
│ │ │ │ ├── repositories/ # Contratos de repositórios do domínio fórum
│ │ │ │ ├── storage/ # Interfaces para armazenamento de arquivos
│ │ │ │ └── use-cases/ # Regras de aplicação e orquestração de lógica
│ │ │ │ └── errors/ # Erros específicos dos casos de uso
│ │ │ ├── enterprise/ # Regras de negócio e entidades do domínio fórum
│ │ │ │ ├── entities/ # Entidades como Topic, Post, Comment, etc.
│ │ │ │ │ └── value-objects/ # Objetos de valor (ex: Slug, UniqueEntityID)
│ │ │ │ └── events/ # Eventos de domínio (ex: OnNewAnswerCreated)
│ │ ├── notification/ # Domínio responsável por notificações
│ │ │ ├── application/ # Casos de uso e contratos da camada de aplicação
│ │ │ │ ├── repositories/ # Interfaces para persistência das notificações
│ │ │ │ ├── subscribers/ # Escutadores de eventos e reações do domínio
│ │ │ │ └── use-cases/ # Lógicas de envio e recebimento de notificações
│ │ │ └── enterprise/ # Entidades e regras do domínio de notificações
│ │ │ └── entities/
│ ├── infra/ # Camada de infraestrutura e implementação
│ │ ├── auth/ # Implementações relacionadas à autenticação
│ │ ├── cache/
│ │ │ └── redis/ # Gerenciamento de cache com Redis
│ │ ├── cryptography/ # Implementações concretas de criptografia (ex: bcrypt)
│ │ ├── database/
│ │ │ └── prisma/ # Integração com Prisma ORM
│ │ │ ├── mappers/ # Conversão entre entidades de domínio e ORM
│ │ │ └── repositories/ # Implementações reais dos repositórios
│ │ ├── env/ # Variáveis e helpers de ambiente
│ │ ├── events/ # Infraestrutura para publicar e escutar eventos
│ │ ├── http/ # Interface HTTP da aplicação
│ │ │ ├── controllers/ # Controllers para exposição de endpoints
│ │ │ ├── pipes/ # Pipes personalizados para validação e transformação
│ │ │ ├── presenters/ # Apresentadores (DTOs de resposta)
│ │ ├── storage/ # Implementações de armazenamento em nuvem/local
│ │ ├── app.module.ts # Módulo raiz da aplicação NestJS
│ │ └── main.ts # Ponto de entrada da aplicação
├── test/ # Testes automatizados
│ ├── factories/ # Fábricas para geração de objetos de teste
│ ├── repositories/ # Implementações em memória dos repositórios
│ └── utils/ # Funções auxiliares para os testes
- 🟩 Node.js
- 📦 pnpm
- 🐳 Docker
- ☁️ Conta na Cloudflare (utilizada para o serviço de storage via R2)
-
Clone o repositório:
git clone https://github.com/joschonarth/nest-clean-api.git
-
Acesse o diretório do projeto:
cd nest-clean-api
-
Instale as dependências:
pnpm install
-
Crie um arquivo
.env
a partir do exemplo:cp .env.example .env
-
Preencha as variáveis do
.env
conforme necessário:DATABASE_URL
: URL de conexão com o PostgreSQL.CLOUDFLARE_ACCOUNT_ID
: Disponível no dashboard da Cloudflare (seção R2).AWS_BUCKET_NAME
: Nome do bucket R2 (ex:nest-clean-api
).AWS_ACCESS_KEY_ID
/AWS_SECRET_ACCESS_KEY
: Gere em R2 > Access Keys.JWT_PRIVATE_KEY
/JWT_PUBLIC_KEY
: Gere com os comandos abaixo.
As chaves são utilizadas para assinatura e verificação de tokens JWT com algoritmo RS256. Os valores devem ser codificados em base64 antes de serem adicionados ao .env
.
# Gerar a chave privada (2048 bits)
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# Gerar a chave pública
openssl rsa -pubout -in private_key.pem -out public_key.pem
# Codificar em base64 e exportar para o .env
echo "JWT_PRIVATE_KEY=\"$(base64 -w 0 private_key.pem)\"" >> .env
echo "JWT_PUBLIC_KEY=\"$(base64 -w 0 public_key.pem)\"" >> .env
# Limpeza (opcional)
rm private_key.pem public_key.pem
# Gerar as chaves
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
# Codificar as chaves
base64 -w 0 private_key.pem > private_key.txt
base64 -w 0 public_key.pem > public_key.txt
# Copie o conteúdo dos arquivos para as variáveis no .env:
# JWT_PRIVATE_KEY="conteúdo de private_key.txt"
# JWT_PUBLIC_KEY="conteúdo de public_key.txt"
# Limpeza (opcional)
rm private_key.pem public_key.pem
💡 Nota: Use
-w 0
nobase64
para evitar quebras de linha. Se estiver no macOS e não tiver-w
, usebase64 -i
.
-
Crie um arquivo
.env.test
a partir do exemplo:cp .env.test.example .env.test
-
Configure as variáveis específicas:
- AWS_BUCKET_NAME: Bucket exclusivo para testes (ex:
nest-clean-api-test
) - REDIS_DB=1: Define o banco Redis alternativo para testes, evitando conflitos com o ambiente de dev.
- AWS_BUCKET_NAME: Bucket exclusivo para testes (ex:
Para usar o serviço de armazenamento R2 da Cloudflare:
- Acesse o Cloudflare Dashboard.
- Vá até a seção R2 no painel lateral.
- Clique em “Criar bucket”.
- Escolha um nome para o bucket (ex:
nest-clean-api
). - Após criar, copie:
- O nome do bucket
- O CLOUDFLARE_ACCOUNT_ID
- Acesse Access Keys > Criar chave e copie:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- Adicione todas essas informações ao seu arquivo
.env
.
-
Inicie os bancos de dados PostgreSQL e Redis utilizando o Docker:
docker-compose up -d
-
Execute as migrações do banco de dados:
npx prisma migrate dev
-
Inicie a API:
pnpm run start:dev
A aplicação estará disponível em http://localhost:3333.
Método | Rota | Descrição | Parâmetros |
---|---|---|---|
POST | /accounts |
Criar uma nova conta de usuário | – |
POST | /sessions |
Autenticar usuário (login) | – |
Método | Rota | Descrição | Parâmetros |
---|---|---|---|
POST | /questions |
Criar uma nova pergunta | – |
PUT | /questions/:id |
Editar uma pergunta | :id (ID da pergunta) |
DELETE | /questions/:id |
Deletar uma pergunta | :id (ID da pergunta) |
GET | /questions |
Listar perguntas recentes | – |
GET | /questions/:slug |
Buscar pergunta por slug | :slug (slug da pergunta) |
Método | Rota | Descrição | Parâmetros |
---|---|---|---|
POST | /questions/:questionId/comments |
Comentar em uma pergunta | :questionId (ID da pergunta) |
GET | /questions/:questionId/comments |
Listar comentários de uma pergunta | :questionId (ID da pergunta) |
DELETE | /questions/comments/:id |
Deletar um comentário de pergunta | :id (ID do comentário) |
Método | Rota | Descrição | Parâmetros |
---|---|---|---|
POST | /questions/:questionId/answers |
Responder uma pergunta | :questionId (ID da pergunta) |
PUT | /answers/:id |
Editar uma resposta | :id (ID da resposta) |
DELETE | /answers/:id |
Deletar uma resposta | :id (ID da resposta) |
GET | /questions/:questionId/answers |
Listar respostas de uma pergunta | :questionId (ID da pergunta) |
PATCH | /answers/:answerId/choose-as-best |
Escolher melhor resposta para pergunta | :answerId (ID da resposta) |
Método | Rota | Descrição | Parâmetros |
---|---|---|---|
POST | /answers/:answerId/comments |
Comentar em uma resposta | :answerId (ID da resposta) |
GET | /answers/:answerId/comments |
Listar comentários de uma resposta | :answerId (ID da resposta) |
DELETE | /answers/comments/:id |
Deletar um comentário de resposta | :id (ID do comentário) |
Método | Rota | Descrição | Parâmetros |
---|---|---|---|
POST | /attachments |
Fazer upload de anexos | – |
GET | /notifications/:notificationId/read |
Marcar notificação como lida | :notificationId (ID da notificação) |
-
Método:
POST
-
URL:
/accounts
-
Corpo da Requisição:
{ "name": "John Doe", "email": "john@example.com", "password": "123456" }
-
Método:
POST
-
URL:
/sessions
-
Corpo da Requisição:
{ "email": "john@example.com", "password": "123456" }
-
Método:
POST
-
URL:
/questions
-
Corpo da Requisição:
{ "title": "Como funciona o NestJS?", "content": "Alguém pode explicar como o NestJS organiza o código?", "attachments": ["uuid-do-arquivo-1"] }
-
Método:
PUT
-
URL:
/questions/:id
-
Parâmetros de Rota:
id
(string): ID da pergunta.
-
Corpo da Requisição:
{ "title": "Atualização: Como funciona o NestJS?", "content": "Adicionei mais detalhes sobre os módulos.", "attachments": ["uuid-do-arquivo-1"] }
- Método:
DELETE
- URL:
/questions/:id
- Parâmetros de Rota:
id
(string): ID da pergunta.
- Método:
GET
- URL:
/questions?page=1
- Parâmetros de Query:
page
(number): Número da página.
- Método:
GET
- URL:
/questions/:slug
- Parâmetros de Rota:
slug
(string): Slug da pergunta.
-
Método:
POST
-
URL:
/questions/:questionId/comments
-
Parâmetros de Rota:
questionId
(string): ID da pergunta.
-
Corpo da Requisição:
{ "content": "Poderia esclarecer melhor a pergunta?" }
- Método:
GET
- URL:
/questions/:questionId/comments?page=1
- Parâmetros de Rota:
questionId
(string): ID da pergunta.
- Parâmetros de Query:
page
(number): Número da página.
- Método:
DELETE
- URL:
/questions/comments/:id
- Parâmetros de Rota:
id
(string): ID do comentário de pergunta.
-
Método:
POST
-
URL:
/questions/:questionId/answers
-
Parâmetros de Rota:
questionId
(string): ID da pergunta.
-
Corpo da Requisição:
{ "content": "Aqui está a minha resposta para a pergunta.", "attachments": ["uuid-do-arquivo-1", "uuid-do-arquivo-2"] }
-
Método:
PUT
-
URL:
/answers/:id
-
Parâmetros de Rota:
id
(string): ID da resposta.
-
Corpo da Requisição:
{ "content": "Atualizei minha resposta com mais informações.", "attachments": ["uuid-do-arquivo-1", "uuid-do-arquivo-2"] }
- Método:
DELETE
- URL:
/answers/:id
- Parâmetros de Rota:
id
(string): ID da resposta.
- Método:
GET
- URL:
/questions/:questionId/answers?page=1
- Parâmetros de Rota:
questionId
(string): ID da pergunta.
- Parâmetros de Query:
page
(number): Número da página.
- Método:
PATCH
- URL:
/answers/:answerId/choose-as-best
- Parâmetros de Rota:
answerId
(string): ID da resposta.
-
Método:
POST
-
URL:
/answers/:answerId/comments
-
Parâmetros de Rota:
answerId
(string): ID da resposta.
-
Corpo da Requisição:
{ "content": "Excelente resposta! Concordo totalmente." }
- Método:
GET
- URL:
/answers/:answerId/comments?page=1
- Parâmetros de Rota:
answerId
(string): ID da resposta.
- Parâmetros de Query:
page
(number): Número da página.
- Método:
DELETE
- URL:
/answers/comments/:id
- Parâmetros de Rota:
id
(string): ID do comentário de resposta.
- Método:
POST
- URL:
/attachments
- Método:
GET
- URL:
/notifications/:notificationId/read
- Parâmetros de Rota:
notificationId
(string): ID da notificação.
Este projeto inclui testes unitários e testes E2E (end-to-end) para garantir a confiabilidade e o funcionamento correto dos recursos implementados. Para executar os testes, utilize os seguintes comandos:
-
Executar testes unitários:
npm run test
-
Executar testes unitários em modo de observação:
npm run test:watch
-
Preparar o ambiente do Prisma antes dos testes E2E:
npm run pretest:e2e
-
Executar testes E2E:
npm run test:e2e
-
Executar testes E2E em modo de observação:
npm run test:e2e:watch
-
Executar testes com cobertura:
npm run test:coverage
-
Executar a interface do usuário do Vitest:
npm run test:ui
Contribuições são muito bem-vindas! Sinta-se à vontade para abrir issues ou pull requests com melhorias ou correções. 🚀
Se você gostou da aplicação, deixe uma ⭐ no repositório!