Setting Up .NET 8 Minimal API with DDD

Sunday OladiranSunday Oladiran
4 min read

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, repositories

  • DTOs/, 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.

0
Subscribe to my newsletter

Read articles from Sunday Oladiran directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sunday Oladiran
Sunday Oladiran