Skip to content
Slicekit

Concepts

Architecture overview

Vertical slice architecture, the .NET projects and the patterns that divide them, the React SPA, and how a feature is added across both sides.

View .md
On this page

Vertical slices, in one minute

Most codebases are organised by layer: a controllers folder, a services folder, a repositories folder, a models folder. A single feature is then smeared across all of them, so changing one behaviour means editing four places and reading code that serves a hundred unrelated features.

Vertical slice architecture turns that ninety degrees. Code is organised by feature, not by technical layer. Everything one use case needs (its request, its logic, its validation, its result) lives together in one folder. The slice you reason about as a human is the slice in the code, so a feature is something you can read, change or delete in one place.

Organised by layer

Controllers
Services
Repositories
Models

One feature cuts through every layer. To change it you touch four places.

Organised by feature

CreateApiKey
Login
ExportData

One feature is one column. Read it, change it or delete it in one place.

Slicekit organises by feature, not by layer, so the slice you reason about is the slice in the code.

The benefits are practical: less coupling between unrelated features, changes that stay local, an obvious place for every piece of code, and a shape a new engineer (or an AI assistant) can learn once and apply everywhere. For the deeper rationale, Jimmy Bogard’s Vertical Slice Architecture is the canonical write-up.

Slicekit applies this shape on both sides of the wire: the .NET API is slice-per-feature, and so is the React SPA.

The API, project by project

The API is vertical slice + domain-driven design + event-driven CQRS on EF Core and PostgreSQL. It is deliberately not a clean-architecture onion of many layered projects, and there is no repository abstraction: the AppDbContext is the unit of work and the repository.

The whole API is two source projects, plus four test projects that guard them.

Slicekit.Api

the host: endpoints, middleware, composition root

Slicekit.Core no dependency on the web host

Features/

vertical slices: command, handler, validator, result

Domain/

aggregates that raise events, primitives

Persistence/

AppDbContext, EF configurations, migrations

Messaging

Wolverine CQRS, transactional outbox

Four test projects guard it

Unit.Tests

logic in isolation

Architecture.Tests

boundaries hold

Feature.Tests

real Postgres

Api.Tests

HTTP end to end

The host depends on the core, never the reverse, so a CLI or a worker can invoke the same commands. Four test projects keep the structure honest.
  • Slicekit.Api is the host. It owns HTTP: endpoints, middleware, the composition root, OpenAPI. Endpoints are thin adapters that map a route, its policies (authorization, validation, rate limiting, CSRF) and its status codes.
  • Slicekit.Core holds everything else: the feature slices under Features/, the domain under Domain/, persistence under Persistence/, and the messaging configuration. It has no dependency on the web host.

That one-way dependency is the point. Because handlers are invoked over Wolverine’s message bus rather than called from controllers, the API is just one possible host. A CLI tool, a background worker or a scheduled job can dispatch the exact same commands without touching any HTTP code.

Two domain rules are worth stating up front, because they keep the model honest:

  • Aggregates raise events. Business rules live in aggregates, which enforce their own invariants and raise a domain event to record what happened. Raising is a fact inside the domain; it sends nothing.
  • Dispatchers publish events. After the change commits, raised events are published to interested handlers and to the transactional outbox.

How commands, queries, events and the outbox fit together is its own page: CQRS and domain events.

React SPA

TanStack · typed client

.NET 10 API

vertical slices · CQRS

PostgreSQL

EF Core

RabbitMQ

outbox · events

Grafana

traces · logs · metrics

One typed client between the apps; the API owns persistence, messaging and telemetry.

At runtime the picture is simple: the SPA talks to the API through one typed client, and the API owns persistence, messaging and telemetry.

The frontend

The SPA mirrors the API’s slice-per-feature shape. Each feature is a folder under features/ that owns its typed client calls, its TanStack Query hooks, its localized Zod schemas and its components. Shared primitives (the UI kit, the API client, theme handling) live in components/ and lib/; anything feature-specific stays inside the feature.

api/ · Features/ApiKeys/CreateApiKey/

  • Command.cs the request shape
  • Handler.cs domain logic
  • Validator.cs input rules
  • Result.cs the response shape

frontend/ · features/api-keys/

  • api.ts typed client call
  • hooks.ts query + mutation
  • schemas.ts zod, localized
  • components/ the UI
Learn one slice and you can navigate every slice; the layout is the same on either side.

It is a Vite + React 19 app in strict TypeScript, with TanStack Router for type-safe routing, TanStack Query for server state, and shadcn/ui on Tailwind. Every request goes through one typed client that handles cookie sessions, the CSRF header and a silent refresh-and-retry on 401. See the frontend overview and the typed API client.

Adding a feature, end to end

Because both sides share the slice shape, one feature is one slice through the whole system. The path is always the same:

  1. Write the slice in Slicekit.Core. A new folder under Features/ with the command (a plain record), the handler (the logic, returning a Result<T>), validation, and the result type. The handler loads an aggregate, calls a domain method, and the aggregate raises its event.
  2. Map a thin endpoint in Slicekit.Api. Declare the route, its permission, rate limit, validation and CSRF as policy, then hand the command to the Wolverine bus. No logic here.
  3. Mirror the slice on the frontend. A folder under features/ with the typed api.ts call, the hooks.ts query and mutation, localized schemas.ts, and the components that render it.
  4. Lean on the guardrails. Architecture tests fail the build if a slice reaches across a feature boundary, and feature tests run against a real PostgreSQL via Testcontainers.

The full step-by-step lives in the two recipes: adding a vertical slice on the API, and the matching frontend recipe in the docs.

Proven, not experimental

Slicekit runs on mainstream technology with years (some decades) behind it: PostgreSQL, Redis, RabbitMQ, relational data, CQRS, domain-driven design, REST and JWT, on the current stable releases of .NET and React. The patterns are battle-tested and the dependencies are ones you can hire for and reason about. There is no exotic runtime and nothing bleeding-edge that breaks on the next upgrade: modern where it helps you, boring where it protects you.

What is deliberately absent

  • No clean-architecture onion. Two source projects, not a stack of layered ones.
  • No repository pattern. The AppDbContext is the unit of work and the repository.
  • No data-migration shims. Slicekit is a template with no existing data to bridge.
  • No bespoke runtime. Everything is standard .NET, EF Core and Wolverine.