A lightweight .NET 10 based Clean Architecture template leveraging Minimal API and .NET Aspire. This repository helps you quickly set up a maintainable project structure that separates core business rules from infrastructure and presentation concerns while taking advantage of Aspire's cloud-native tooling.
- Faster Development: Pre-configured with essential design patterns like CQRS (Commands/Queries), value objects, and layered architecture.
- Domain-Driven Design: Organize your core business logic with Entities, Value Objects, Specifications, and Domain Services.
- Cloud-Ready with .NET Aspire: Uses an Aspire AppHost to orchestrate services, dependencies, health checks, and observability.
- Postgres by Default: Uses PostgreSQL as the primary relational database (via EF Core) for local development and deployment.
- Simple API: Minimal API endpoints keep things lightweight and easy to extend.
- Extensible: Easily add new modules, microservices, or features without breaking existing code.
- Scalable: Clear separation of concerns keeps your code well-organized as your app grows.
- Robust Testing: Includes example unit tests (using xUnit and Moq) and integration tests.
- Polly Integration: Resilient API calls with built-in retries, circuit breaker patterns, and fallbacks.
- Domain Layer
- Entities, value objects, and domain services.
- Application Layer
- Interfaces, DTOs, commands, queries, and validators.
- Infrastructure Layer
- Persistence, database repositories, and external service integrations.
- Infrastructure.Persistence Layer
- EF Core
DbContext, Postgres configuration, and migrations.
- EF Core
- Presentation Layer
- Minimal API endpoints, middleware, and request/response handling.
- Aspire AppHost
.NET AspireAppHost project that wires up the API, Postgres, and shared service defaults.
The following prerequisites are required to build and run the solution:
- .NET 10.0 SDK
- .NET Aspire workload (for local AppHost support)
- Docker (for running Postgres when orchestrated by Aspire)
- Clone the repository to your local machine.
git clone https://github.com/iPazooki/CleanArchitecture.gitgit clone git@github.com:iPazooki/CleanArchitecture.git
- Navigate to the project directory.
- Restore dependencies using:
dotnet restore
The solution is configured to run using .NET Aspire via the AppHost project.
- From the solution root, run the AppHost project:
dotnet run --project CleanArchitecture.Aspire/CleanArchitecture.AppHost/CleanArchitecture.AppHost.csproj
- Aspire will:
- Start the
CleanArchitecture.Presentationminimal API. - Provision and start a Postgres container/instance (as configured in the AppHost).
- Apply service defaults (health checks, telemetry, etc.).
- Start the
- Once running, navigate to the API Swagger UI (the port may vary depending on Aspire configuration). By default this is similar to:
https://localhost:7281/swagger/index.html
If you prefer to run only the API (and manage Postgres yourself):
- Ensure you have a running Postgres instance and that the connection string in
CleanArchitecture.Presentation/API/appsettings.jsonpoints to it. - Build and run the application:
dotnet builddotnet run --project CleanArchitecture.Presentation
- Visit the Swagger UI (by default):
https://localhost:7281/swagger/index.html
The Aspire AppHost is configured for Azure deployment through azd.
- Azure CLI
- Azure Developer CLI (
azd) - .NET Aspire workload
From the solution root, run:
azd upDuring deployment, azd will provision the Azure resources defined by CleanArchitecture.Aspire/CleanArchitecture.AppHost/AppHost.cs, including:
- Azure Container Apps for the API, admin app, and Keycloak
- Azure Database for PostgreSQL Flexible Server
- Azure Key Vault
- Azure Application Insights
azd up should prompt only for values that cannot be derived automatically:
nextAuthSecret
keycloakClientId
keycloakClientSecret
keycloakRealm
keycloakAdminUsername
keycloakAdminPassword
The following public application URLs are now derived automatically from the Azure Container Apps external endpoints during manifest generation and deployment:
adminPublicUrlapiPublicUrlauthPublicUrl
You should no longer need to enter those values manually during azd up.
If you later want to use custom domains instead of the default Azure Container Apps hostnames, update the production environment configuration in CleanArchitecture.Aspire/CleanArchitecture.AppHost/AppHost.cs and your Azure Container Apps ingress/domain settings accordingly.
This project integrates with Keycloak for authentication. Before running the API with secured endpoints you must provide a client secret for the scalar client in Keycloak and store it in the project's user secrets.
Important: for Visual Studio users you can set secrets via the Manage User Secrets UI. The instructions below assume the client in Keycloak is named scalar.
- Open your Keycloak admin panel (the instance URL configured for your environment) and select
clean-apirealm. - Go to the
Clientssection and select thescalarclient. - Open the
Credentials(orClient Secrets) tab for that client. - Create/regenerate a new client secret and copy the secret value.
- In Visual Studio, right-click the
CleanArchitecture.Apiproject and select Manage User Secrets. - In the JSON editor that opens, add an entry for the client secret. Example structure:
{ "ScalarApi:ClientSecret": "YourClientSecret" }
- Use the
dotnet user-secretsCLI tool to set the secret from the terminal/command prompt:
dotnet user-secrets set "ScalarApi:ClientSecret" "<your-client-secret-here>" --project CleanArchitecture.ApiThe project includes an admin dashboard located in CleanArchitecture.Presentation/admin. To run the admin portal, you need to configure the environment variables in a .env.local file.
- Navigate to the admin directory:
cd CleanArchitecture.Presentation/admin - Create or update the
.env.localfile with the following configurations:
API_BASE_URL=http://localhost:5049/
NEXTAUTH_URL=http://localhost:65499
NEXTAUTH_SECRET=<random string>
KEYCLOAK_CLIENT_ID=scalar
KEYCLOAK_CLIENT_SECRET=<client secret>
KEYCLOAK_ISSUER=http://localhost:8080/realms/clean-api
- API_BASE_URL: The base URL where your CleanArchitecture API is running.
- NEXTAUTH_URL: The URL of your admin portal (used for authentication callbacks).
- NEXTAUTH_SECRET: A random secret string used to hash tokens. You can generate one using
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". - KEYCLOAK_CLIENT_ID: The client ID configured in Keycloak (e.g.,
scalar). - KEYCLOAK_CLIENT_SECRET: The client secret from your Keycloak client's Credentials tab.
- KEYCLOAK_ISSUER: The issuer URL of your Keycloak realm (e.g.,
http://localhost:8080/realms/clean-api).
- Database: PostgreSQL
- EF Core Provider:
Npgsql.EntityFrameworkCore.PostgreSQL - Configuration: Connection strings are defined in
CleanArchitecture.Presentation/API/appsettings.jsonand can be overridden in environment-specific files likeCleanArchitecture.Presentation/API/appsettings.Development.jsonor via Aspire resource configuration inCleanArchitecture.Aspire/CleanArchitecture.AppHost/appsettings*.json.
Migrations are managed using Entity Framework Core and are stored in the CleanArchitecture.Infrastructure.Persistence project.
Because .NET Aspire injects connection strings at runtime, the standard EF Core tools cannot find a database connection during design-time. To solve this, the project includes an ApplicationDbContextFactory which provides a dummy connection string for the tools.
To add a new migration, run the following command from the solution root:
dotnet ef migrations add <MigrationName> --project CleanArchitecture.Infrastructure.Persistence --startup-project CleanArchitecture.Presentation/APIIn non-production environments, migrations are automatically applied during application startup using context.Database.MigrateAsync() in CleanArchitecture.Presentation/API/Configuration/WebApplicationExtensions.cs.
If you need to manually update the database:
dotnet ef database update --project CleanArchitecture.Infrastructure.Persistence --startup-project CleanArchitecture.Presentation/APIEnsure a PostgreSQL instance is reachable if you are running this manually outside of the Aspire orchestrator.
Pull requests are welcome! For major changes, please open an issue first to discuss proposed modifications. We appreciate your support and feedback. Don’t forget to star the project if you find it helpful.
This project is licensed under the MIT license. Feel free to use it as a foundation for your own projects!