NeonVertexApi é uma API REST de propósito geral desenvolvida para servir de base para projetos pessoais. O objetivo é ter uma fundação sólida, bem estruturada e reutilizável — com autenticação, gerenciamento de usuários e uma arquitetura que escala de forma organizada conforme o projeto cresce, sem a complexidade desnecessária de microserviços.
O projeto foi construído com foco em clareza de código, convenções consistentes e facilidade de extensão. Cada decisão arquitetural foi tomada priorizando a compreensão do que está acontecendo, sem magia excessiva ou abstrações desnecessárias.
| Tecnologia | Versão | Papel |
|---|---|---|
| .NET | 10 | Runtime |
| ASP.NET Core | 10 | Framework web |
| PostgreSQL | 17 | Banco de dados |
| Entity Framework Core | 10 | ORM |
| Npgsql | 10 | Driver PostgreSQL para EF Core |
| BCrypt.Net-Next | — | Hash de senhas |
| Scalar | — | Documentação interativa (OpenAPI) |
O projeto adota o padrão Monólito Modular — uma abordagem que combina a simplicidade operacional de um monólito com a organização e separação de responsabilidades de uma arquitetura modular.
Ao contrário dos microserviços, onde cada domínio é um serviço independente com sua própria infraestrutura, o monólito modular mantém tudo em um único processo mas organiza o código em módulos bem delimitados que se comunicam por interfaces, não por acoplamento direto.
- Simplicidade operacional — um único artefato para fazer deploy, uma única conexão com o banco, sem latência de rede entre serviços
- Organização por domínio — cada módulo é dono do seu código, sem que outros módulos conheçam seus detalhes internos
- Escalabilidade de código — conforme o projeto cresce, novos módulos são adicionados sem impactar os existentes
- Caminho para microserviços — se um domínio precisar escalar independentemente no futuro, a separação já está feita conceitualmente
NeonVertexApi/
├── App/
│ ├── Core/ # Infraestrutura transversal da aplicação
│ │ ├── Authentication/ # TokenService, CurrentUserService
│ │ ├── Database/ # AppDbContext, Migrations
│ │ │ └── Migrations/
│ │ ├── Extensions/ # ServiceCollectionExtensions, WebApplicationExtensions
│ │ ├── Middleware/ # ExceptionMiddleware
│ │ └── Settings/ # JwtSettings
│ │
│ ├── Shared/ # Contratos e utilitários compartilhados entre módulos
│ │ ├── Exceptions/ # AppException com factory methods por status HTTP
│ │ ├── Interfaces/ # ICurrentUser, IUsersRepository
│ │ ├── Models/ # Result<T>, PagedList<T>
│ │ └── Validators/ # Base de validação
│ │
│ └── Modules/ # Domínios da aplicação
│ ├── Authentication/ # Login, logout, contexto do usuário autenticado
│ │ ├── Controllers/
│ │ ├── DTOs/
│ │ ├── Services/
│ │ └── AuthModule.cs
│ └── Users/ # Gerenciamento de usuários
│ ├── Controllers/
│ ├── DTOs/
│ ├── Models/
│ ├── Repositories/
│ ├── Services/
│ └── UsersModule.cs
│
├── Program.cs
├── appsettings.json
├── Dockerfile
├── compose.yaml
└── Taskfile.yaml
Core contém tudo que é infraestrutura técnica — classes que conversam com o .NET, com o banco, com o pipeline HTTP. Não há regra de negócio aqui. O AppDbContext vive aqui, assim como o middleware de exceção e a configuração de autenticação JWT.
Shared contém contratos e utilitários que qualquer módulo pode precisar, mas que não pertencem a nenhum módulo específico. A distinção com o Core é que o Shared não sabe que existe ASP.NET — é C# puro. As interfaces que um módulo expõe para os outros ficam aqui.
Modules contém os domínios da aplicação. Cada módulo é fechado internamente e expõe apenas o que precisa através de interfaces declaradas em Shared. A comunicação entre módulos acontece sempre por interfaces registradas no container de DI, nunca por acoplamento direto entre implementações.
ModuleName/
├── Controllers/ # Recebe a request HTTP, delega pro service, retorna a response
├── DTOs/ # Objetos de entrada (CreateDto, UpdateDto) e saída (NameResponse)
│ # DTOs de saída têm método estático FromEntity para mapear da entidade
├── Models/ # Entidade de domínio com propriedades private set e factory method Create
│ # Configuração do EF Core via IEntityTypeConfiguration<T>
├── Repositories/ # Acesso ao banco via AppDbContext, implementa interface de Shared
├── Services/ # Regras de negócio, orquestra repository e outras dependências
└── ModuleNameModule.cs # Extension method que registra tudo do módulo no container de DI
HTTP Request
└── Controller # valida entrada, chama service
└── Service # aplica regras de negócio, lança AppException se necessário
└── Repository # consulta ou persiste no banco via EF Core
└── Controller # retorna response com status HTTP adequado
HTTP Response
Em caso de erro:
AppException → ExceptionMiddleware → JSON com { message, toast }
Erros de negócio são comunicados via AppException, uma exception com factory methods semânticos que interrompem o fluxo e são interceptados pelo ExceptionMiddleware:
// No service — interrompe o fluxo imediatamente
throw AppException.NotFound("Usuário não encontrado.");
throw AppException.Conflict("Email já está em uso.");
throw AppException.Forbidden("Acesso negado.");O middleware formata automaticamente a resposta JSON com o contrato de toast, consumido pelo interceptor do frontend Angular:
{
"message": "Email já está em uso.",
"toast": {
"type": "warning",
"message": "Email já está em uso."
}
}O tipo do toast é resolvido automaticamente pela faixa do status HTTP — error para 5xx, warning para 4xx.
A autenticação utiliza JWT armazenado em HTTP-only cookie, o que significa que o token nunca fica acessível via JavaScript, protegendo contra ataques XSS. O browser envia o cookie automaticamente em cada request, sem necessidade de lógica explícita no frontend além de um interceptor de CORS.
O token expira em 24 horas. Não há refresh token — ao expirar, o usuário precisa fazer login novamente.
- .NET 10 SDK
- PostgreSQL 17
- Task (opcional, para comandos facilitados)
- Docker (opcional, para produção)
git clone https://github.com/seu-usuario/NeonVertexApi.git
cd NeonVertexApiCopie o arquivo de exemplo e preencha com suas credenciais:
cp .env.example .envPOSTGRES_CONNECTION=Host=;Port=5432;Database=;Username=;Password=
JWT__SECRET=
JWT__ISSUER=NeonVertexApi
JWT__AUDIENCE=NeonVertexApi
JWT__EXPIRATIONHOURS=24O desenvolvimento local utiliza o mecanismo de User Secrets do .NET, que armazena credenciais fora do repositório em %APPDATA%\Microsoft\UserSecrets\:
dotnet user-secrets set "ConnectionStrings:Default" "Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=senha"
dotnet user-secrets set "Jwt:Secret" "seu-secret-com-pelo-menos-32-caracteres"No Visual Studio, o gerenciador de User Secrets está disponível clicando com o botão direito no projeto → Manage User Secrets.
dotnet restoreAs migrations são aplicadas automaticamente ao iniciar a aplicação via MigrateAsync() no Program.cs. Não é necessário nenhum comando manual em produção.
Para criar uma nova migration após alterar ou adicionar uma entidade:
dotnet ef migrations add NomeDaMigrationPara listar o status das migrations:
dotnet ef migrations listPara reverter a última migration:
dotnet ef migrations removeO EF Core tools precisa estar instalado:
dotnet tool install --global dotnet-ef
dotnet runA API estará disponível em:
https://localhost:7209http://localhost:5148
A documentação interativa (Scalar) estará disponível em:
https://localhost:7209/scalar/v1
O Dockerfile utiliza multi-stage build — a imagem de produção parte da imagem aspnet sem SDK, resultando em uma imagem final enxuta.
Em produção, a API é exposta via Nginx Proxy Manager na rede Docker interna, sem expor portas diretamente no host.
task build # rebuilda a imagem sem cache
task up # sobe em background
task deploy # build + up em um comandotask logs # exibe logs em tempo real
task restart # reinicia o container
task shell # abre shell no container
task down # derruba os containers{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "João Silva",
"email": "joao@email.com",
"isActive": true,
"isEmailVerified": false,
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": null,
"lastLoginAt": null
}{
"message": "Email já está em uso.",
"toast": {
"type": "warning",
"message": "Email já está em uso."
}
}{
"message": "Erro interno do servidor.",
"toast": {
"type": "error",
"message": "Erro interno do servidor."
}
}- Namespaces espelham a estrutura de pastas:
NeonVertexApi.App.{Camada}.{Módulo} - Entidades usam
private setem todas as propriedades — estado só é alterado por métodos da própria entidade - Entidades são criadas via factory method estático
Create, nunca por construtor público - DTOs de saída são
recordimutáveis com método estáticoFromEntitypara conversão - Erros de negócio usam
AppExceptioncom factory methods semânticos (NotFound,Conflict, etc.) - Todas as datas são armazenadas e retornadas em UTC
- Nomes de tabelas e colunas seguem snake_case no banco via
UseSnakeCaseNamingConvention - Cada módulo registra seus próprios serviços via extension method
AddNomeModule
- Crie a pasta
App/Modules/NomeModulocom a estrutura padrão - Declare a entidade em
Models/comprivate sete factory methodCreate - Configure o mapeamento EF Core via
IEntityTypeConfiguration<T> - Adicione o
DbSet<Entidade>noAppDbContext - Declare a interface do repository em
Shared/Interfaces/ - Implemente o repository em
Modules/NomeModulo/Repositories/ - Implemente o service em
Modules/NomeModulo/Services/ - Crie o controller em
Modules/NomeModulo/Controllers/ - Registre tudo em
NomeModuloModule.cse chamebuilder.Services.AddNomeModulo()noProgram.cs - Rode
dotnet ef migrations add NomeDaMigrationpara criar a migration
Este projeto está sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.