Skip to content

About

A full-stack backend template that wires together the most common enterprise concerns so you can focus on building features, not infrastructure.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

CleanArcStack

Production-ready .NET 9 Clean Architecture boilerplate — batteries included.

.NET License: MIT GitHub

A full-stack backend template that wires together the most common enterprise concerns so you can focus on building features, not infrastructure.


✨ Feature Highlights

Concern Solution
Architecture Clean Architecture — Domain → Application → Infrastructure → API
Runtime .NET 9
Database PostgreSQL 16 via EF Core 9 + Npgsql
Messaging MassTransit 8 + RabbitMQ (Outbox pattern — guaranteed delivery)
Caching Redis 7 (StackExchange.Redis)
Search Elasticsearch 8 (Elastic.Clients.Elasticsearch)
Object Storage MinIO (S3-compatible, presigned URL pattern, dual-client CDN)
CQRS Martinothamar/Mediator — source-generator based, zero reflection
Validation FluentValidation — pipeline behavior, 422 field-level errors
Mapping Mapster — static extension methods in Application layer
Auth ASP.NET Core Identity + JWT Bearer + API Key (custom scheme)
Logging Serilog → Console + Elasticsearch sink, enriched with TraceId & SpanId
Tracing OpenTelemetry → Elastic APM Server 8 (OTLP/HTTP)
Log/Trace UI Kibana 8 (logs dashboard + APM traces)
API Docs Scalar (OpenAPI) — Development only
API Versioning Asp.Versioning.Mvc
Health Checks AspNetCore.HealthChecks.UI — /health/live, /health/ready, /health-ui
Resilience Polly v8 (Microsoft.Extensions.Http.Resilience)
Reverse Proxy Nginx — :80 API proxy, :9080 MinIO CDN with proxy_cache
Testing xUnit + FluentAssertions + NSubstitute + Testcontainers + NetArchTest

🏗️ Architecture

Domain ──► Application ──► Infrastructure ──► API

Strict one-way dependency — inner layers never reference outer layers.

src/
├── CleanArcStack.Domain/           # Entities, Value Objects, Domain Events, Repository interfaces
├── CleanArcStack.Application/      # CQRS handlers, validators, pipeline behaviors, interfaces
├── CleanArcStack.Infrastructure/   # EF Core, Redis, RabbitMQ, Elasticsearch, MinIO, JWT, Identity
└── CleanArcStack.API/              # Controllers (thin dispatchers), middleware, DI wiring

tests/
├── CleanArcStack.Unit.Tests/       # Domain, Application handlers, Architecture tests (NetArchTest)
└── CleanArcStack.Integration.Tests/# Testcontainers-based real infrastructure tests

Key Patterns

  • Result Pattern — Result<T> returned from every Application handler; no exceptions for expected failures
  • MassTransit Outbox — integration events published transactionally via PostgreSQL-backed outbox
  • Presigned URLs — MinIO dual-client pattern: management client (internal) + CDN client (public hostname)
  • API Key middleware — SHA-256 hashed keys validated before JWT auth; negative cache in Redis
  • Refresh token cookie — HttpOnly; Secure; SameSite=Strict, never in response body
  • Idempotency middleware — Idempotency-Key header replays cached responses for non-idempotent POSTs

🚀 Quick Start

Prerequisites

1 — Clone

git clone https://github.com/mhmtdev/CleanArcStack.git
cd CleanArcStack

2 — Create secrets

All sensitive values live in .secrets/ — never in .env or appsettings.json.

💡 Quickest way: run chmod +x setup-secrets.sh && ./setup-secrets.sh — generates all required files with random values in one step. Use the manual commands below only if you need custom values.

mkdir -p .secrets

# PostgreSQL
echo -n "YourStrongPostgresPassword"     > .secrets/postgres_password

# Connection strings
echo -n "Host=postgres;Port=5432;Database=cleanarcstack;Username=postgres;Password=YourStrongPostgresPassword" \
                                          > .secrets/ConnectionStrings__DefaultConnection.docker
echo -n "Host=localhost;Port=5432;Database=cleanarcstack;Username=postgres;Password=YourStrongPostgresPassword" \
                                          > .secrets/ConnectionStrings__DefaultConnection

# JWT (min 32 chars)
echo -n "YourSuperSecretJwtKeyAtLeast32Chars!" > .secrets/Jwt__Secret

# RabbitMQ
echo -n "YourRabbitMQPassword"           > .secrets/RabbitMQ__Password

# Elasticsearch
echo -n "YourElasticPassword"            > .secrets/Elasticsearch__Password

# Kibana
echo -n "YourKibanaSystemPassword"       > .secrets/kibana_system_password
echo -n "$(openssl rand -hex 32)"        > .secrets/kibana_encryption_key
echo -n "$(openssl rand -hex 32)"        > .secrets/kibana_security_encryption_key
echo -n "$(openssl rand -hex 32)"        > .secrets/kibana_reporting_encryption_key

# MinIO
echo -n "your-minio-access-key"          > .secrets/MinIO__AccessKey
echo -n "your-minio-secret-key"          > .secrets/MinIO__SecretKey

# APM (same value for both files)
echo -n "$(openssl rand -hex 20)"        > .secrets/Apm__SecretToken
cp .secrets/Apm__SecretToken               .secrets/Telemetry__ApmSecretToken

⚠️ Always use echo -n (no trailing newline) — a trailing newline breaks AddKeyPerFile.

3 — Start infrastructure

docker compose up -d

This starts: PostgreSQL · Redis · RabbitMQ · Elasticsearch · Kibana · APM Server · MinIO · Nginx

The API container auto-runs EF Core migrations and seeds roles + API keys on first boot.

4 — Run the API locally

dotnet run --project src/CleanArcStack.API --launch-profile https
URL Description
https://localhost:7279/scalar Scalar API documentation
https://localhost:7279/health-ui Health Checks dashboard
https://localhost:7279/health/live Liveness probe
https://localhost:7279/health/ready Readiness probe

Or via Docker (Nginx on :5001):

docker compose up -d --build api
# API: http://localhost:5001
# Health: http://localhost:5001/health/live

🔑 Getting Your First API Key

On startup, the seeder creates two API keys (Frontend + Mobile) — hashed in the database.
To retrieve plaintext values, use the dev-only endpoint:

POST https://localhost:7279/api/v1/dev/api-keys/regenerate

Response:

{
  "success": true,
  "data": [
    { "name": "Frontend Web Client", "clientType": "Frontend", "rawKey": "...", "usage": "X-Api-Key: ..." },
    { "name": "Mobile Client",       "clientType": "Mobile",   "rawKey": "...", "usage": "X-Api-Key: ..." }
  ]
}

Use the rawKey value in the X-Api-Key header on every request.

This endpoint returns 404 in Production — Development only.


🔐 Authentication Flow

POST /api/v1/auth/register   → 201  { userId }
POST /api/v1/auth/login      → 200  { accessToken, expiresIn }  +  Set-Cookie: refreshToken (HttpOnly)
POST /api/v1/auth/refresh    → 200  new accessToken  (reads cookie, no body needed)
POST /api/v1/auth/revoke     → 204  (clears cookie)
GET  /api/v1/auth/me         → 200  { userId, email, roles }
  • Access token — JWT, 1 hour, returned in response body
  • Refresh token — 7 days, HttpOnly; Secure; SameSite=Strict cookie at path /api/v1/auth
  • API Key — X-Api-Key header, validated before JWT middleware (SHA-256 hashed in DB)

🐳 Docker Services

Service Host Port Purpose
PostgreSQL 16 5432 Primary database
Redis 7 6379 Cache + idempotency keys
RabbitMQ 3 5672 / 15672 Messaging (management UI)
Elasticsearch 8.12 9200 Search + Serilog log sink
Kibana 8.12 5601 Log visualization + APM UI
APM Server 8.12 8200 Receives OTLP traces
MinIO 9000 (S3) / 9001 (Console) Object storage
Nginx 5001 (API) / 9080 (MinIO CDN) Reverse proxy

🧪 Testing

# All tests
dotnet test CleanArcStack.sln

# Unit tests only (fast — no I/O)
dotnet test tests/CleanArcStack.Unit.Tests/

# Integration tests (Testcontainers — requires Docker)
dotnet test tests/CleanArcStack.Integration.Tests/

Test Strategy

Layer Approach
Domain Pure unit tests — entity logic, domain events, invariants
Application NSubstitute mocks + FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing)
Infrastructure Testcontainers — real PostgreSQL, Redis, RabbitMQ containers
Architecture NetArchTest — layer dependency rules enforced on every build

⚙️ Configuration Reference

Non-sensitive defaults live in appsettings.json. Sensitive values come from .secrets/ (local) or Docker secrets (/run/secrets) in production.

{
  "ConnectionStrings": { "DefaultConnection": "", "Redis": "localhost:6379" },
  "Jwt": { "Issuer": "CleanArcStack API", "Audience": "CleanArcStack", "ExpirySeconds": 3600 },
  "RabbitMQ": { "Host": "localhost", "VirtualHost": "/", "Username": "cleanarcstack_app" },
  "Elasticsearch": { "Url": "http://localhost:9200", "Username": "elastic" },
  "MinIO": { "Endpoint": "localhost:9000", "CdnBaseUrl": "http://localhost:9080", "UseSSL": false },
  "Telemetry": { "ServiceName": "CleanArcStack.API", "ApmServerUrl": "" }
}

🗄️ Database Migrations

# Add a new migration (run from repo root)
dotnet ef migrations add YYYYMMDD_Description \
  --project src/CleanArcStack.Infrastructure \
  --startup-project src/CleanArcStack.API

# Migrations run automatically on startup via MigrateAsync()

📁 Project Structure Details

src/CleanArcStack.Domain/
├── Common/             AggregateRoot, BaseEntity, IDomainEvent
├── Entities/           AppUser, ApiKey, RefreshToken
├── Enums/
├── Events/             Domain event definitions
├── Exceptions/         DomainException, typed error classes
├── Repositories/       IRepository<T>, IUnitOfWork interfaces
└── ValueObjects/

src/CleanArcStack.Application/
├── Common/
│   ├── Behaviors/      LoggingBehavior → ValidationBehavior → PerformanceBehavior → CachingBehavior
│   ├── Exceptions/     AppException, ValidationException, NotFoundException, ConflictException
│   ├── Interfaces/     ICacheService, ICurrentUser, IStorageService, IAuthService, ITokenService …
│   └── Models/         Result<T>, Error, PagedResult<T>, ApiResponse<T>
└── Features/
    └── Auth/           Login, Register, Refresh, Revoke, GetMe commands/queries + DTOs

src/CleanArcStack.Infrastructure/
├── Caching/            RedisCacheService
├── Identity/           JwtTokenService, AuthService, ApiKeyService, CurrentUser
├── Messaging/          MassTransit consumers, integration events, publishers
├── Persistence/        AppDbContext, EF Core configurations, migrations, seeders
├── Search/             ElasticsearchService, ElasticsearchHealthCheck
├── Settings/           JwtSettings, RabbitMQSettings, ElasticsearchSettings, StorageSettings, TelemetrySettings
└── Storage/            MinioStorageService (dual-client), StorageHealthCheck

src/CleanArcStack.API/
├── Common/             ApiResponse<T>, ApiError, PaginationMeta
├── Controllers/        ApiControllerBase, AuthController, DevController
├── Extensions/         ApiKeyAuthHandler, ClaimsPrincipalExtensions
└── Middleware/         ApiKeyMiddleware, CorrelationIdMiddleware, GlobalExceptionHandler, IdempotencyMiddleware

🔄 Pipeline Behavior Order

Registered outermost → innermost:

Request
  └── LoggingBehavior        (logs request/response, elapsed time)
        └── ValidationBehavior  (FluentValidation → 422 on failure)
              └── PerformanceBehavior  (warns on slow handlers)
                    └── CachingBehavior  (Redis — only for ICacheQuery)
                          └── Handler

🌐 Middleware Order

ExceptionHandler → ForwardedHeaders → CORS → CorrelationId
→ Security Headers → ApiKeyMiddleware → HttpsRedirection (dev)
→ RateLimiter → Authentication → Authorization → Idempotency
→ MapControllers

📊 Observability

  • Structured logs → Serilog Console + Elasticsearch sink
  • Log correlation → TraceId + SpanId enriched via Serilog.Enrichers.Span
  • Distributed traces → OpenTelemetry OTLP → Elastic APM Server
    • Auto-instruments: ASP.NET Core, EF Core, HttpClient, Redis, MassTransit
  • Kibana → http://localhost:5601
    • Discover: structured log search
    • Observability → APM: distributed traces per service

🤖 AI-Assisted Development

CleanArcStack is designed to work seamlessly with AI coding assistants. The repository ships with first-class support for GitHub Copilot and is compatible with Claude, Cursor, and any assistant that reads markdown instruction files.

How It Works

The project architecture, coding conventions, layer rules, naming patterns, and design decisions are codified in machine-readable instruction files. AI assistants pick these up automatically, so every suggestion they make stays consistent with the codebase — no need to repeat context in every chat.

.github/
├── copilot-instructions.md      # Global rules — every Copilot Chat session reads this
├── instructions/                # Layer-specific rules loaded by pattern matching
│   ├── domain.instructions.md
│   ├── application.instructions.md
│   ├── infrastructure.instructions.md
│   ├── api.instructions.md
│   └── testing.instructions.md
└── prompts/                     # Reusable agent tasks (GitHub Copilot Agent Mode)
    ├── add-feature.prompt.md    # Scaffold a full vertical slice across all layers
    ├── add-migration.prompt.md  # Create an EF Core migration with validation
    ├── check-arch.prompt.md     # Audit Clean Architecture compliance
    ├── infra-up.prompt.md       # Start Docker infra and verify service health
    └── run-tests.prompt.md      # Run tests and report results

AGENTS.md    # OpenAI Codex / GPT-based agents
CLAUDE.md    # Claude (Anthropic)

Using the Prompts (GitHub Copilot Agent Mode)

Open Copilot Chat, switch to Agent Mode, then reference any prompt file with #:

Prompt What It Does How to Invoke
add-feature Scaffolds a complete vertical slice — Domain entity, Application command/query + validator, Infrastructure EF config + repository + migration, API controller, unit tests #add-feature.prompt.md → enter feature name
add-migration Builds the project, creates a dated EF Core migration, reviews the generated Up/Down #add-migration.prompt.md → enter description
check-arch Scans all layers for Clean Architecture violations (wrong dependencies, missing ConfigureAwait, naked Ok(), etc.) and runs NetArchTest #check-arch.prompt.md
infra-up Starts Docker Compose, waits for health checks, reports service status #infra-up.prompt.md
run-tests Runs unit and/or integration tests, summarises failures with stack traces #run-tests.prompt.md → optionally enter a filter

Example — add a new feature in Agent Mode:

  1. Open Copilot Chat → Agent Mode
  2. Type: #add-feature.prompt.md
  3. Enter feature name when prompted, e.g. Child
  4. Copilot generates all files across Domain → Application → Infrastructure → API → Tests, runs the build, creates the migration, and verifies compliance — without asking for confirmation at each step.

💡 The instruction files also work passively: simply open a file in any layer and start chatting — Copilot automatically applies the correct layer rules without any prompt.

Supported AI Assistants

Assistant Configuration File Notes
GitHub Copilot .github/copilot-instructions.md + .github/instructions/*.md Auto-loaded; pattern-matched per file
Claude (Anthropic) CLAUDE.md + .claude/commands/ Repo root — auto-loaded by Claude Code and claude.ai Projects
OpenAI / GPT agents AGENTS.md Codex / custom GPT instructions
Cursor .github/copilot-instructions.md Cursor reads Copilot instructions natively

🤖 Claude (Anthropic) — CLAUDE.md Reference

CLAUDE.md at the repository root is automatically picked up by Claude Code and claude.ai Projects. It encodes the full architecture, critical gotchas, and coding conventions so Claude produces consistent, compliant code without requiring repeated context.

Using Slash Commands (Claude Code)

The .claude/commands/ directory contains reusable slash commands for Claude Code. Type / in Claude Code to invoke them:

Command What It Does
/add-feature <name> Scaffolds a complete vertical slice — Domain entity, Application command/query + validator, Infrastructure EF config + repository + migration, API controller, unit tests
/add-migration <description> Builds the project, creates a dated EF Core migration, reviews the generated Up/Down
/check-arch Scans all layers for Clean Architecture violations (wrong dependencies, missing ConfigureAwait, naked Ok(), etc.) and runs NetArchTest
/infra-up Starts Docker Compose, waits for health checks, reports service status and URLs
/run-tests <filter?> Runs unit and/or integration tests, summarises failures with stack traces
.claude/
├── commands/
│   ├── add-feature.md       # /add-feature <featureName>
│   ├── add-migration.md     # /add-migration <description>
│   ├── check-arch.md        # /check-arch
│   ├── infra-up.md          # /infra-up
│   └── run-tests.md         # /run-tests [filter]
└── settings.json

Example — add a new feature with Claude Code:

  1. Open Claude Code in the project directory
  2. Type: /add-feature Child
  3. Claude generates all files across Domain → Application → Infrastructure → API → Tests, runs the build, creates the migration, and verifies compliance — without asking for confirmation at each step.
View full CLAUDE.md reference (architecture rules, patterns, gotchas)

Stack

.NET 9 · Clean Architecture · PostgreSQL 16 (EF Core 9) · Redis · RabbitMQ + MassTransit · Elasticsearch 8 · MinIO (S3) · Serilog → ES · OpenTelemetry → Elastic APM · Martinothamar/Mediator (NOT MediatR) · FluentValidation · Mapster · Scalar (OpenAPI) · xUnit + FluentAssertions + NSubstitute + Testcontainers

Architecture — Layer Rules

Domain → Application → Infrastructure → API
Layer Project May NOT reference
Domain CleanArcStack.Domain Application, Infrastructure, API, EF Core, Redis, RabbitMQ
Application CleanArcStack.Application Infrastructure, API, EF Core, Redis, RabbitMQ
Infrastructure CleanArcStack.Infrastructure API
API CleanArcStack.API Direct DB/cache/queue calls — only DI wiring

⚠️ Critical Gotchas

1 — Mediator is NOT MediatR

MediatR This project
using MediatR; using Mediator;
ISender IMediator
Task<T> in handlers ValueTask<T> in handlers
services.AddMediatR(...) services.AddMediator(...)

AddMediator(...) lives in API's DependencyInjection.cs — NOT in Application.

2 — ConfigureAwait(false)

  • Infrastructure layer: required on every async call.
  • Application, Domain, API layers: do NOT add.

3 — Result pattern — never throw for expected failures

public async ValueTask<Result<Guid>> Handle(CreateOrderCommand request, CancellationToken ct)
{
    var order = Order.Create(...);
    repository.Add(order);
    await unitOfWork.SaveChangesAsync(ct);
    return order.Id.Value; // implicit conversion T → Result<T>
}

// Error codes in English, messages in Turkish
public static readonly Error NotFound = new("Order.NotFound", "Sipariş bulunamadı.");

4 — TimeProvider, never DateTime.UtcNow — domain methods receive TimeProvider as parameter.

5 — Guid.CreateVersion7(), never Guid.NewGuid()

6 — MassTransit Outbox is mandatory — never publish integration events directly from command handlers.

Command / Query Pattern

// Command + Validator in the same file
public sealed record CreateOrderCommand(Guid CustomerId, IReadOnlyList<CreateOrderItemDto> Items)
    : IRequest<Result<Guid>>;

public sealed class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderCommandValidator()
    {
        RuleFor(x => x.CustomerId).NotEmpty();
        RuleFor(x => x.Items).NotEmpty().WithMessage("Sipariş en az bir kalem içermelidir.");
    }
}

public sealed class CreateOrderCommandHandler(
    IOrderRepository repository,
    IUnitOfWork unitOfWork,
    TimeProvider timeProvider
) : IRequestHandler<CreateOrderCommand, Result<Guid>>
{
    public async ValueTask<Result<Guid>> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        var order = Order.Create(new CustomerId(request.CustomerId), timeProvider);
        repository.Add(order);
        await unitOfWork.SaveChangesAsync(ct);
        return order.Id.Value;
    }
}

Every Command/Query must have a paired validator — no exceptions.

Domain Entity Pattern

public sealed class Order : AggregateRoot
{
    private readonly List<OrderItem> _items = [];
    public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();

    private Order() { } // EF Core

    public static Order Create(CustomerId customerId, TimeProvider time)
    {
        var order = new Order { Id = Guid.CreateVersion7(), ... };
        order.RaiseDomainEvent(new OrderCreatedDomainEvent(order.Id));
        return order;
    }
}
  • Private/protected constructor + static Create(...) factory
  • No public setters — state via domain methods
  • Strongly typed IDs: public sealed record OrderId(Guid Value);
  • EF Core config requires HasConversion(id => id.Value, v => new OrderId(v))

Controller Pattern

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[Authorize]
public sealed class OrdersController(IMediator mediator) : ApiControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateOrderRequest request, CancellationToken ct)
    {
        var result = await mediator.Send(new CreateOrderCommand(request.CustomerId, request.Items), ct);
        return result.IsSuccess
            ? CreatedResponse("GetOrderById", new { id = result.Value }, result.Value)
            : ErrorResponse(result.Error);
    }

    [HttpGet("{id:guid}", Name = "GetOrderById")]
    public async Task<IActionResult> GetById(Guid id, CancellationToken ct) =>
        FromResult(await mediator.Send(new GetOrderByIdQuery(id), ct));
}

No try/catch in controllers — GlobalExceptionHandler handles unhandled exceptions.

Naming Conventions

Artifact Pattern Example
Command {Action}{Entity}Command CreateOrderCommand
Query {Get|List|Search}{Entity}Query GetOrderByIdQuery
Handler {Action}{Entity}CommandHandler CreateOrderCommandHandler
Domain Event {Entity}{Action}DomainEvent OrderCreatedDomainEvent
Integration Event {Entity}{Action}IntegrationEvent OrderCreatedIntegrationEvent
Consumer {Entity}{Action}IntegrationEventConsumer OrderCreatedIntegrationEventConsumer
Error class {Entity}Errors (static) OrderErrors
Response DTO {Entity}Response OrderResponse
EF Config {Entity}Configuration OrderConfiguration

Code Style

  • sealed on all Commands, Queries, Handlers, Validators, Services (unless inheritance needed)
  • record for Commands, Queries, DTOs, Domain Events, Value Objects, Integration Events
  • Primary constructors for DI — no manual field assignment
  • required on mandatory DTO/settings properties
  • File-scoped namespaces — namespace MyApp.Domain.Entities;
  • Collection expressions [...] — never new List<T> { }
  • IReadOnlyList<T> from public methods — never expose List<T> directly
  • ArgumentNullException.ThrowIfNull / ArgumentException.ThrowIfNullOrWhiteSpace for guards
  • Settings injection: always IOptions<TSettings> — never IConfiguration in service classes
  • Error codes: "{Entity}.{Reason}" (English) — descriptions in Turkish
  • Mapster mapping in Application layer only (static extension methods next to DTOs)

Testing Rules

  • Unit tests: no real I/O — NSubstitute mocks or concrete fakes; FakeTimeProvider for time-dependent logic.
  • Integration tests: Testcontainers always — never UseInMemoryDatabase in integration tests.
  • UseInMemoryDatabase acceptable only in unit tests for Infrastructure services (e.g. AuthServiceTests).
  • Test method naming: {Method}_{Scenario}_{ExpectedResult}
  • AssemblyMarker must exist in every project.
  • Guid.CreateVersion7() in test data — never Guid.NewGuid().

Important File Locations

File Purpose
src/CleanArcStack.API/Program.cs Startup · secrets loading · middleware order · public partial class Program;
src/CleanArcStack.API/DependencyInjection.cs AddMediator · CORS · rate limiter · health-checks UI
src/CleanArcStack.Infrastructure/DependencyInjection.cs All infrastructure registrations
src/CleanArcStack.Application/DependencyInjection.cs Pipeline behaviors (AddScoped) · AddValidatorsFromAssembly
src/CleanArcStack.API/Controllers/ApiControllerBase.cs OkResponse · ErrorResponse · FromResult · PagedResponse
src/CleanArcStack.API/Controllers/AuthController.cs Cookie handling pattern for refresh/revoke
src/CleanArcStack.Application/Common/Models/Result.cs Result pattern
src/CleanArcStack.Infrastructure/Persistence/Configurations/ EF Core entity configs
tests/CleanArcStack.Unit.Tests/Common/ArchitectureTests.cs Architecture validation tests
tests/CleanArcStack.Integration.Tests/Infrastructure/IntegrationTestFixture.cs Testcontainers fixture

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Follow the architecture and code style rules in .github/copilot-instructions.md
  4. Add/update tests
  5. Open a pull request

📄 License

MIT — see LICENSE for details.

About

A full-stack backend template that wires together the most common enterprise concerns so you can focus on building features, not infrastructure.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages