Este projeto é uma API REST desenvolvida com NestJS aplicando os princípios da Clean Architecture (Arquitetura Limpa). O projeto demonstra como implementar uma arquitetura robusta, testável e escalável para aplicações Node.js, incorporando práticas de Domain-Driven Design (DDD) e SOLID.
- Demonstrar a implementação da Clean Architecture em NestJS
- Aplicar conceitos de Domain-Driven Design (DDD)
- Criar uma estrutura de código testável e manutenível
- Implementar autenticação JWT
- Usar Prisma como ORM
- Configurar testes automatizados (unitários, integração e e2e)
O projeto segue a estrutura da Clean Architecture, dividida em camadas bem definidas:
┌─────────────────────────────────────────────────────────────┐
│ Interface Layer │
│ (Controllers, DTOs, Presenters) │
├─────────────────────────────────────────────────────────────┤
│ Application Layer │
│ (UseCases, Services, DTOs) │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ (Entities, Repositories, Validators) │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ (Database, External Services, Guards) │
└─────────────────────────────────────────────────────────────┘
- Dependency Inversion: As camadas internas não dependem das externas
- Single Responsibility: Cada classe tem uma única responsabilidade
- Open/Closed: Aberto para extensão, fechado para modificação
- Interface Segregation: Interfaces específicas para cada necessidade
- Liskov Substitution: Subtipos devem ser substituíveis por seus tipos base
src/
├── main.ts # Ponto de entrada da aplicação
├── app.module.ts # Módulo principal
├── global-config.ts # Configurações globais
│
├── shared/ # Código compartilhado
│ ├── application/ # Camada de aplicação compartilhada
│ │ ├── dtos/ # Data Transfer Objects
│ │ ├── errors/ # Erros customizados da aplicação
│ │ ├── providers/ # Interfaces de provedores
│ │ └── usecases/ # Interface base para casos de uso
│ │
│ ├── domain/ # Camada de domínio compartilhada
│ │ ├── entities/ # Entidades base
│ │ ├── errors/ # Erros de domínio
│ │ ├── repositories/ # Contratos e implementações base
│ │ └── validators/ # Validadores de domínio
│ │
│ └── infrastructure/ # Camada de infraestrutura compartilhada
│ ├── database/ # Configuração do banco de dados
│ ├── env-config/ # Configuração de ambiente
│ ├── exception-filters/ # Filtros de exceção
│ ├── interceptors/ # Interceptadores
│ └── presenters/ # Apresentadores de dados
│
├── users/ # Módulo de usuários
│ ├── application/ # Casos de uso dos usuários
│ │ ├── dtos/ # DTOs específicos de usuários
│ │ └── usecases/ # Casos de uso (signup, signin, etc.)
│ │
│ ├── domain/ # Domínio dos usuários
│ │ ├── entities/ # Entidade User
│ │ ├── repositories/ # Contrato do repositório de usuários
│ │ └── validators/ # Validadores de usuário
│ │
│ └── infrastructure/ # Infraestrutura dos usuários
│ ├── users.controller.ts # Controlador REST
│ ├── users.module.ts # Módulo NestJS
│ ├── database/ # Implementação do repositório
│ ├── dtos/ # DTOs da API
│ └── presenters/ # Apresentadores de resposta
│
└── auth/ # Módulo de autenticação
└── infrastructure/ # Implementação de autenticação
├── auth.guard.ts # Guard JWT
├── auth.module.ts # Módulo de autenticação
└── auth.service.ts # Serviço de autenticação
- Node.js 18+
- NestJS 9 - Framework web
- TypeScript - Linguagem de programação
- Fastify - Servidor HTTP (mais performático que Express)
- Prisma - ORM moderno para TypeScript
- PostgreSQL - Banco de dados relacional
- JWT (JSON Web Tokens) - Autenticação stateless
- bcryptjs - Hash de senhas
- class-validator - Validação de DTOs
- class-transformer - Transformação de objetos
- Jest - Framework de testes
- Supertest - Testes de integração HTTP
- Swagger/OpenAPI - Documentação automática da API
- Docker - Containerização
- Docker Compose - Orquestração de containers
- Node.js 18+
- Docker e Docker Compose
- npm ou yarn
- Clone o repositório
git clone <repository-url>
cd modelo_nestjs_clean_arch
- Instale as dependências
npm install
- Configure as variáveis de ambiente
# Crie os arquivos de ambiente
cp .env.example .env.development
cp .env.example .env.test
cp .env.example .env
- Inicie o banco de dados
docker-compose up -d
- Execute as migrações
npx prisma migrate dev
- Inicie a aplicação
npm run start:dev
A aplicação estará disponível em http://localhost:3000
npm run start:dev # Inicia em modo desenvolvimento
npm run start:debug # Inicia em modo debug
npm run build # Gera build de produção
npm run start:prod # Inicia em modo produção
npm run test # Executa todos os testes
npm run test:unit # Executa testes unitários
npm run test:int # Executa testes de integração
npm run test:e2e # Executa testes end-to-end
npm run test:cov # Executa testes com coverage
npm run test:watch # Executa testes em modo watch
npm run lint # Executa ESLint
npm run format # Formata código com Prettier
A camada mais interna, contendo as regras de negócio puras.
// src/shared/domain/entities/entity.ts
export abstract class Entity<Props = any> {
public readonly _id: string
public readonly props: Props
constructor(props: Props, id?: string) {
this.props = props
this._id = id || randomUUID()
}
// ...
}
// src/users/domain/entities/user.entity.ts
export class UserEntity extends Entity<UserProps> {
constructor(public readonly props: UserProps, id?: string) {
UserEntity.validate(props)
super(props, id)
this.props.createdAt = this.props.createdAt ?? new Date()
}
update(value: string): void {
UserEntity.validate({ ...this.props, name: value })
this.name = value
}
// ...
}
// src/users/domain/repositories/user.repository.ts
export namespace UserRepository {
export interface Repository
extends SearchableRepositoryContract<
UserEntity,
UserFilter,
SearchParams,
SearchResult
> {
findByEmail(email: string): Promise<UserEntity>
emailExists(email: string): Promise<void>
}
}
Contém os casos de uso e regras de aplicação.
// src/users/application/usecases/signup.usecase.ts
export namespace SignupUseCase {
export type Input = {
name: string
email: string
password: string
}
export type Output = UserOutput
export class UseCase implements DefaultUseCase<Input, Output> {
constructor(
private userRepository: UserRepository.Repository,
private hashProvider: HashProvider,
) {}
async execute(input: Input): Promise<Output> {
// Validações
// Regras de negócio
// Persistência
}
}
}
Implementa os detalhes técnicos e integrações externas.
// src/users/infrastructure/users.controller.ts
@ApiTags('users')
@Controller('users')
export class UsersController {
@Inject(SignupUseCase.UseCase)
private signupUseCase: SignupUseCase.UseCase
@Post()
async create(@Body() signupDto: SignupDto) {
const output = await this.signupUseCase.execute(signupDto)
return UsersController.userToResponse(output)
}
// ...
}
O sistema utiliza JWT (JSON Web Tokens) para autenticação:
POST /users/login
{
"email": "[email protected]",
"password": "password123"
}
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Get('me')
async profile() {
// Rota protegida
}
O projeto possui configurações separadas para diferentes tipos de teste:
jest.unit.config.ts
- Testes unitáriosjest.int.config.ts
- Testes de integraçãojest.e2e.config.ts
- Testes end-to-end
Testam unidades isoladas (entities, use cases, services):
describe('UserEntity', () => {
it('should create a user with valid data', () => {
const userData = {
name: 'John Doe',
email: '[email protected]',
password: 'password123'
}
const user = new UserEntity(userData)
expect(user.name).toBe('John Doe')
expect(user.email).toBe('[email protected]')
})
})
Testam a integração entre camadas:
describe('SignupUseCase', () => {
it('should create a new user', async () => {
const repository = new InMemoryUserRepository()
const hashProvider = new BcryptjsHashProvider()
const useCase = new SignupUseCase.UseCase(repository, hashProvider)
const result = await useCase.execute({
name: 'John Doe',
email: '[email protected]',
password: 'password123'
})
expect(result.name).toBe('John Doe')
})
})
Testam fluxos completos da API:
describe('/users (e2e)', () => {
it('should create a user', async () => {
return request(app.getHttpServer())
.post('/users')
.send({
name: 'John Doe',
email: '[email protected]',
password: 'password123'
})
.expect(201)
})
})
// Wrapper para padronizar responses
@UseInterceptors(WrapperDataInterceptor)
// Tratamento centralizado de erros
@UseFilters(ConflictErrorFilter)
@UseFilters(NotFoundErrorFilter)
- Criar a Entidade de Domínio
// domain/entities/product.entity.ts
export class ProductEntity extends Entity<ProductProps> {
// Implementar regras de negócio
}
- Definir o Contrato do Repositório
// domain/repositories/product.repository.ts
export namespace ProductRepository {
export interface Repository {
// Definir métodos necessários
}
}
- Implementar os Casos de Uso
// application/usecases/create-product.usecase.ts
export namespace CreateProductUseCase {
export class UseCase {
// Implementar lógica do caso de uso
}
}
- Criar o Controlador
// infrastructure/product.controller.ts
@Controller('products')
export class ProductController {
// Implementar endpoints
}
- Implementar o Repositório
// infrastructure/database/prisma/product-prisma.repository.ts
export class ProductPrismaRepository implements ProductRepository.Repository {
// Implementar persistência
}
- Configurar o Módulo
// infrastructure/product.module.ts
@Module({
providers: [
// Configurar providers
],
controllers: [ProductController]
})
export class ProductModule {}
Método | Endpoint | Descrição | Auth |
---|---|---|---|
POST | /users |
Criar usuário | ❌ |
POST | /users/login |
Login | ❌ |
GET | /users |
Listar usuários | ✅ |
GET | /users/:id |
Buscar usuário | ✅ |
PUT | /users/:id |
Atualizar usuário | ✅ |
PATCH | /users/:id |
Atualizar senha | ✅ |
DELETE | /users/:id |
Deletar usuário | ✅ |
- Swagger UI:
http://localhost:3000/api
- Documentação automática de todos os endpoints
- Esquemas de request/response
- Autenticação JWT integrada
# Iniciar apenas o banco
docker-compose up -d
# Build da aplicação
docker build -t nestjs-clean-arch .
# Dockerfile multi-stage para otimização
FROM node:18-alpine AS builder
# ... build steps
FROM node:18-alpine AS production
# ... production setup
- S: Cada classe tem uma responsabilidade específica
- O: Código aberto para extensão, fechado para modificação
- L: Subtipos são substituíveis pelos tipos base
- I: Interfaces segregadas e específicas
- D: Dependência de abstrações, não de implementações
- Separação clara de responsabilidades
- Dependências apontam para dentro
- Regras de negócio isoladas
- Facilita testes e manutenção
- Entities com comportamento
- Value Objects para conceitos sem identidade
- Repositories como contratos
- Services para operações complexas
- Erros customizados por camada
- Exception filters centralizados
- Responses padronizados
- DTOs com class-validator
- Validação na entrada da aplicação
- Validação de domínio nas entities
- Erro de Conexão com Banco
# Verificar se o container está rodando
docker-compose ps
# Recriar o container
docker-compose down && docker-compose up -d
- Erro de Migração
# Reset do banco (desenvolvimento)
npx prisma migrate reset
# Aplicar migrações
npx prisma migrate dev
- Erro de Dependências
# Limpar cache e reinstalar
rm -rf node_modules package-lock.json
npm install
- Fork o 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 UNLICENSED - veja o arquivo LICENSE para detalhes.
- Este projeto foi inicialmente criado pelo Aluizio Developer (https://github.com/aluiziodeveloper/nestjs-clean-arch.git) e alterado/adaptado por Érick Nilson - Desenvolvimento inicial - GitHub
- NestJS Team
- Clean Architecture Community
- Prisma Team
- Contribuidores Open Source