A .NET modular monolith base platform for building multi-tenant SaaS products.
Fork it. Add your domain modules. Deploy.
Pre-release: Wallow has not been deployed outside local development yet, and breaking changes to
main are expected.
- Multi-tenancy from day one -- tenant isolation, per-tenant config, and data partitioning built into the architecture so you ship product features, not plumbing
- Fork-first architecture -- merge drivers preserve your branding, config, and customizations when pulling upstream improvements
- Batteries-included infrastructure -- observability, background jobs, file storage, email, caching, and CI/CD preconfigured and tested
- Comprehensive test coverage -- the .NET suites (unit, integration, architecture) plus per-app Playwright E2E and Vitest browser-mode suites across the pnpm workspace
Wallow provides the cross-cutting infrastructure every SaaS product needs out of the box -- identity, notifications, announcements, file storage, API keys, and multi-tenant data isolation. You write the business logic.
The intended workflow is to fork this repo and build your product on top. Shared infrastructure improvements can be pulled from upstream into forks without conflicts.
New here? Start with the Fork Guide or the Developer Guide.
- .NET 10 SDK
- Docker
- Node 24 (see
.nvmrc) and pnpm 10.20.0 (seepackageManagerinpackage.json)
pnpm installpnpm backend:infra # docker compose up -d; backend:infra:down to stopStarts PostgreSQL, Valkey, GarageHQ (S3), Mailpit, and Grafana.
The frontends are React (TanStack Start) apps; the .NET Aspire host orchestrates the API, both React apps, migrations, and the seeder together:
pnpm backend # Aspire AppHost: API + Auth + Web + Migration + SeederOr run the pieces individually:
dotnet run --project api/src/Wallow.Api # API → http://localhost:5001
pnpm --filter @bc-solutions-coder/wallow-web dev # Web → http://localhost:3000
pnpm --filter @bc-solutions-coder/wallow-auth dev # Auth → http://localhost:3002./scripts/run-tests.sh # backend fast suites (Category=Integration excluded)
./scripts/run-tests.sh all # the same, plus the integration suites (needs Docker)
./scripts/run-tests.sh identity # single backend module
pnpm check # the frontend quality gate (format, lint, build, typecheck, test)See Testing for coverage, E2E, and CI details.
A modular monolith where each module is an autonomous bounded context following Clean Architecture. Modules communicate through Wolverine in-memory events via Shared.Contracts -- never direct references. Each module owns its own PostgreSQL schema.
api/
└── src/
├── Wallow.Api/ # REST API host: modules, Wolverine, OpenIddict resource server
├── Wallow.AppHost/ # .NET Aspire host orchestrating the API, React apps, and infra
├── Wallow.MigrationService/ # Applies EF migrations for all module DbContexts
├── Wallow.SeederService/ # Seeds roles, scopes, admin, and OIDC clients from seed.json
├── Wallow.ServiceDefaults/ # Aspire defaults: telemetry, health checks, resilience
├── Modules/
│ ├── Identity/ # Auth, users, organizations, RBAC
│ ├── Storage/ # File storage (S3-compatible)
│ ├── Notifications/ # In-app and push notifications
│ ├── Announcements/ # System-wide announcements
│ ├── Inquiries/ # Inquiry and question submission
│ ├── ApiKeys/ # API key management
│ └── Branding/ # Tenant branding configuration
└── Shared/
├── Wallow.Shared.Contracts/ # Cross-module integration events
├── Wallow.Shared.Kernel/ # DDD primitives, multi-tenancy, JWT claim helpers
├── Wallow.Shared.Api/ # Shared API utilities (Result → IActionResult, health check)
├── Wallow.Shared.Infrastructure/ # Settings framework and module coordination
├── Wallow.Shared.Infrastructure.Core/ # Persistence, caching, and messaging primitives
├── Wallow.Shared.Infrastructure.BackgroundJobs/ # Hangfire-backed IJobScheduler
└── Wallow.Shared.Infrastructure.Plugins/ # Plugin loading and extension points
apps/ # React (TanStack Start) frontends (pnpm workspace)
├── wallow-auth/ # Auth UI (login, register, MFA) → http://localhost:3002
├── wallow-web/ # Dashboard and public pages → http://localhost:3000
└── minimal-app/ # Smallest wiring of the shared packages → http://localhost:3010
packages/ # Shared TypeScript packages (SDK, UI, forms, auth, styles, …)
Each module follows four layers: Domain (no dependencies) → Application → Infrastructure → API.
Deep dive: Architecture Assessment · Module Creation
| Feature | Description |
|---|---|
| Clean Architecture | Strict dependency rules per module with domain isolation |
| Domain-Driven Design | Entities, value objects, domain events, bounded contexts |
| CQRS | Command/query separation with Wolverine as mediator |
| Multi-Tenancy | Schema-per-tenant data isolation, configurable resolution (header, subdomain, JWT) |
| Event-Driven | Wolverine in-memory events between modules |
| Identity & RBAC | OpenIddict + ASP.NET Core Identity |
| Real-Time | Push notifications via SignalR |
| Observability | Serilog structured logging, OpenTelemetry tracing, Grafana dashboards |
| Audit Trail | Automatic entity change auditing via Audit.NET |
| Background Jobs | IJobScheduler abstraction backed by Hangfire |
| Purpose | Technology |
|---|---|
| Framework | .NET 10 |
| Database | PostgreSQL 18 |
| ORM | EF Core + Dapper (available for raw SQL reads) |
| CQRS & Messaging | Wolverine (in-memory) |
| Caching | Valkey (Redis-compatible) |
| Identity | OpenIddict + ASP.NET Core Identity |
| Real-time | SignalR |
| Validation | FluentValidation |
| Logging & Tracing | Serilog, OpenTelemetry |
| Testing | xUnit, Testcontainers, AwesomeAssertions |
The backend has 15 xUnit test assemblies under api/tests/ (unit, integration, and architecture),
alongside the shared Wallow.Tests.Common helper library and the BenchmarkDotNet project, neither
of which carries tests. Most pnpm workspace members add a Vitest suite — DOM specs run in a real
browser project, non-DOM specs in a node project — plus per-app Playwright E2E suites.
./scripts/run-tests.sh # backend fast suites, with the coverage runsettings
./scripts/run-tests.sh all # the same, plus every Category=Integration test (Docker)
pnpm check # frontend quality gate, including pnpm test
./scripts/e2e.sh # containerised backend + all three Playwright suitesA bare backend run filters out Category=Integration and says so in its own output; all (or
integration for that tier alone) is what exercises the Wolverine handler-codegen guards and the
Testcontainers-backed suites.
Coverage figures are produced by the run, not tracked here — ./scripts/run-tests.sh writes them
with api/tests/coverage.runsettings applied.
Details: Testing Guide — backend suites, frontend Vitest, coverage, the Docker test stack, and CI · E2E Tests
Wallow is designed to be customized without changing source code. Backend configuration flows through standard .NET mechanisms; branding is frontend-only and lives with the styles package:
| Area | Config Source | What it controls |
|---|---|---|
| Branding | packages/styles/branding.json |
App name, icon, tagline, theme colors |
| Database | appsettings.json |
PostgreSQL and Valkey connection strings |
appsettings.json |
SMTP host, port, TLS, sender defaults | |
| Storage | appsettings.json |
S3 endpoint, bucket, ClamAV virus scanning |
| Observability | appsettings.json |
OpenTelemetry OTLP endpoints, service name |
| CORS | appsettings.json |
Allowed origins for API requests |
| Environment | Environment variables | Override any setting with Section__Key syntax |
Configuration loads in order: appsettings.json → appsettings.{Environment}.json → environment variables → user secrets (dev only).
Full reference with examples for Docker, Kubernetes, and all module options: Configuration Guide
| Service | URL |
|---|---|
| API | http://localhost:5001 |
| API Docs (Scalar) | http://localhost:5001/scalar/v1 |
| Web (TanStack) | http://localhost:3000 |
| Auth (TanStack) | http://localhost:3002 |
| Minimal app | http://localhost:3010 |
| Docs | http://localhost:5004 |
| Mailpit | http://localhost:8025 |
| GarageHQ (S3) | http://localhost:3900 |
| Grafana | http://localhost:3001 |
Credentials and config: Configuration Guide. The application rows are duplicated in root
CLAUDE.md's Local Development table — change both together.
| Guide | Description |
|---|---|
| Developer Guide | Day-to-day development workflow |
| Fork Guide | Creating a new product from Wallow |
| Configuration | Environment variables, branding, settings |
| Architecture | Design decisions and patterns |
| Module Creation | Adding new modules |
| Deployment | CI/CD, Docker, and production setup |
| Versioning | Conventional Commits and release-please |
| Observability | Logging, tracing, and dashboards |
| Frontend Setup | The pnpm workspace, Vite, and TanStack Start |
| Component Library | The shared @bc-solutions-coder/ui catalog |
| Forms | The @bc-solutions-coder/forms authoring layer |
| Frontend State | TanStack Query, auth, and the nav store |
| Logging | Structured logging across the browser and the app server |
| BFF Pattern | Same-origin OIDC through the app server |
| TypeScript SDK | @bc-solutions-coder/sdk reference |