Setting Up .NET 8 Minimal API with DDD


Introduction
In today’s fast-moving software world, building scalable, maintainable, and testable applications is more important than ever.
.NET 8.0 introduces even more power and flexibility with Minimal APIs, enabling developers to quickly build lightweight web services without sacrificing performance or structure.
However, as projects grow, keeping them organized can become a real challenge.
That’s where Domain-Driven Design (DDD) and Clean Architecture come in — helping you separate concerns, model your business logic effectively, and keep technical debt under control.
In this article, we'll walk through how to set up a professional-grade .NET 8 Minimal API project from scratch, applying DDD principles and Clean Architecture patterns.
You'll learn how to structure your codebase the right way, making it easier to scale, test, and evolve over time.
Whether you're starting a new microservice or modernizing a legacy system, this guide will give you a strong foundation for success!
Great initiative! Setting up a new .NET 8.0 Minimal API project using microservices, Clean Architecture, and Domain-Driven Design (DDD) principles is a smart move. Here’s a comprehensive roadmap to get this service up and running with:
✅ Proper folder structure (Clean Architecture + DDD)
✅ Middleware (Exception handling, logging, CORS, etc.)
✅ Security (JWT Authentication and Role-based Authorization)
✅ Shared resources (e.g., common DTOs, base classes, validation)
✅ Infrastructure (EF Core, persistence, DI setup)
✅ Swagger, Serilog, and Polly for resilience
✅ 1. Solution Structure
src/ ├── AddressVerification.API # Minimal API project (entry point) ├── AddressVerification.Application # Application Layer (Use Cases, Interfaces) ├── AddressVerification.Domain # Domain Layer (Entities, Aggregates, Repositories) ├── AddressVerification.Infrastructure # Infra Layer (EF Core, Email, External Services) ├── AddressVerification.Shared # Shared Kernel (Common types, DTOs, exceptions) tests/ ├── AddressVerification.UnitTests
✅ 2. Initialize Solution
Add project references:
dotnet new sln -n AddressVerificationService cd AddressVerificationService
dotnet new web -n AddressVerification.API dotnet new classlib -n AddressVerification.Application dotnet new classlib -n AddressVerification.Domain dotnet new classlib -n AddressVerification.Infrastructure dotnet new classlib -n AddressVerification.Shared
dotnet sln add **/*.csproj
cd AddressVerification.Application dotnet add reference ../AddressVerification.Domain/AddressVerification.Domain.csproj cd ../AddressVerification.Infrastructure dotnet add reference ../AddressVerification.Application/AddressVerification.Application.csproj cd ../AddressVerification.API dotnet add reference ../AddressVerification.Application/AddressVerification.Application.csproj dotnet add reference ../AddressVerification.Infrastructure/AddressVerification.Infrastructure.csproj dotnet add reference ../AddressVerification.Domain/AddressVerification.Domain.csproj dotnet add reference ../AddressVerification.Shared/AddressVerification.Shared.csproj
✅ 3. Add NuGet Packages
For the main project and layers:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.AspNetCore.Authorization
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
dotnet add package FluentValidation
dotnet add package Swashbuckle.AspNetCore
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Console
dotnet add package Polly
✅ 4. Program.cs - Minimal API Setup
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddInfrastructure(builder.Configuration)
.AddApplication()
.AddShared();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.Console()
.ReadFrom.Configuration(ctx.Configuration));
var app = builder.Build();
app.UseSerilogRequestLogging();
app.UseSwagger();
app.UseSwaggerUI();
app.UseCors(policy => policy
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseAuthorization();
app.UseExceptionHandler("/error");
app.MapGroup("/api/v1/address-verification").MapAddressVerificationEndpoints().RequireAuthorization();
app.MapGroup("/api/v1/customer-profile").MapCustomerProfileEndpoints().RequireAuthorization();
app.Run();
✅ 5. DDD + Clean Architecture Layers Overview
Domain/
Entities/
,Aggregates/
,ValueObjects/
IRepository<T>
, Domain Events,BaseEntity
Application/
UseCases/
– e.g.,VerifyAddressHandler.cs
Interfaces/
– Abstractions for services, repositoriesDTOs/
,Validators/
Infrastructure/
Implements
IRepository<T>
Configures EF DbContexts
External service implementations (email, 3rd party APIs)
Shared/
Errors/
,Result<T>
,ApiResponse
,BaseException
Logging helpers, DTOs,
IServiceResult
✅ 6. Authentication & Authorization
Use JWT for token-based auth.
Use policies for Role/Claim-based authorization.
Example:
app.MapPost("/token", async (UserLogin login, ITokenService tokenService) =>
{
var token = tokenService.GenerateToken(login);
return Results.Ok(new { token });
});
✅ 7. Example Middleware (Exception Handler)
app.Map("/error", (HttpContext context) =>
{
var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
return Results.Problem(detail: exception?.Message, statusCode: 500);
});
✅ 8. CI/CD, Database Migrations, Docker, etc.
Create Dockerfile and docker-compose
Setup EF Core migrations in
Infrastructure
Setup GitHub Actions / Azure Pipelines for deployment
Conclusion
Setting up a .NET 8 Minimal API using Domain-Driven Design (DDD) and Clean Architecture requires thoughtful planning and strict adherence to best practices. Without a clear structure, even small projects can quickly become hard to manage as they grow.
By following the steps outlined above, you’ll be able to build a solid, scalable foundation for your applications — one that’s easy to maintain, extend, and optimize over time.
Take your time to carefully practice and apply these principles; the payoff will be a clean, robust system you’ll be proud of.
If you have questions, suggestions, or thoughts, feel free to share them in the comments section — I’d love to hear from you!
Let’s connect! Follow me for more real-world tips on .NET, Minimal APIs, Clean Architecture, and beyond.
Subscribe to my newsletter
Read articles from Sunday Oladiran directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
