A complete, runnable showcase for Kalicz.StrongTypes: a product-reviews platform where every constrained value is a strong type — from the request DTO through the domain and the database, out into the OpenAPI document, and all the way into the generated TypeScript client. No data annotations, no guard clauses, no hand-written frontend types.
It doubles as a starter template for full-stack .NET web apps: ASP.NET Core (controllers) + EF Core/PostgreSQL + .NET Aspire + Zitadel auth + Vue 3, with spec-driven docs, ADRs, and a no-mocks test pyramid. The conventions are documented in CLAUDE.md and docs/.
Prerequisites:
- .NET 10 SDK
- Node.js 20+
- Docker Desktop (running)
One command:
dotnet run --project src/ProductReviews.AppHost(or aspire run if you have the Aspire CLI).
The AppHost starts PostgreSQL, Zitadel (identity provider + hosted login), the API, and the frontend; installs frontend dependencies on first run; applies migrations; and seeds a browsable catalog. First start pulls container images — give it a few minutes; subsequent starts are fast.
Where to click:
- The app: http://localhost:5173 — browse the catalog, open a product, read reviews.
- Sign in to write reviews and vote:
demo@productreviews.local/ProductReviews123!(a second account,critic@productreviews.local, same password, lets you vote on the first account's review — you can never vote on your own). - Swagger UI: http://localhost:5173/swagger — look at
SubmitReviewRequest:ratingcarriesminimum: 1, maximum: 5,titlecarriesminLength: 1,emailon the profile carriesformat: email. Nothing in the codebase states those rules except the types themselves. - Aspire dashboard — the URL is printed on startup; logs, traces, and resource states live there.
| The point | Where |
|---|---|
| DTOs with strong types, zero annotations | ReviewContracts.cs |
Maybe<T> three-state PATCH (omit = keep, {} = clear, {"Value": …} = set) |
EditReviewRequest, Review.ApplyEdit, EditReview.cs |
Result<T, TError> + error enums instead of exceptions |
SubmitReview.cs → mapped exhaustively in ReviewsController.cs |
Declaring your own strong type ([NumericWrapper]) |
Rating.cs |
EF Core storing strong types directly (UseStrongTypes()) |
Persistence.cs, ReviewsDbContext.cs, the migration (plain varchar/int columns) |
OpenAPI carrying the real constraints (AddStrongTypes()) |
OpenApi.cs, snapshot in frontend/openapi.json |
| Constraints flowing into TypeScript (generated client) | schema.d.ts (generated), client.ts, editReviewBody.ts |
| Property-based tests with generated strong-typed values | RatingTests.cs, ReviewEditTests.cs |
| The OpenAPI claims verified against the running API | OpenApiDocumentTests.cs |
Parse-don't-validate at the query boundary (Positive<int> paging, Rating[] filters) |
ReviewsController.cs, GetReviewsPage.cs |
# Backend: domain unit + property tests, then wire-level integration tests
# (Testcontainers spins up a real PostgreSQL — Docker must be running)
dotnet test
# Frontend unit tests (Vitest)
npm --prefix frontend run test:unit
# End-to-end: builds the frontend, boots the WHOLE stack in E2E mode, and
# drives real browsing, sign-in, reviewing, and voting (Playwright)
npm --prefix frontend run test:e2eNothing is mocked anywhere — see ADR-0005.
The implementation follows the docs, not the other way around:
- Business requirements — what the app does, in product terms.
- Technical requirements — architecture, stack, conventions.
- ADRs — the pivotal decisions, one aspect each.
- Assumptions to validate — where the brief allowed more than one reading.
- The library: https://github.com/KaliCZ/StrongTypes
- The idea, explained: Zero-Code Validations in Your .NET API
- NuGet: Kalicz.StrongTypes