Local setup
The backend API is a .NET 10 / ASP.NET Core service backed by PostgreSQL, using a strict database-per-tenant model. This page gets you from a fresh clone to a running API in a few commands, then points you at the canonical developer guide for the detail.
The authoritative, fuller source is NexisOmni/docs/developer-guide.md (in the backend repo). Read it when you need the full architecture tour, the everyday-task recipes, or the gotchas. Run every command below from the solution root (NexisOmni/) in PowerShell.
Prerequisites
Section titled “Prerequisites”- .NET 10 SDK - the projects target
net10.0. - Docker Desktop - runs local PostgreSQL (the central database plus one database per tenant).
- EF Core CLI - pinned as a local tool. Restore it once after cloning:
dotnet tool restoreFirst-time database setup
Section titled “First-time database setup”The system uses two EF Core contexts against two kinds of database: CentralDbContext for the central administration database (platform admins + the tenant registry), and TenantDbContext for each tenant’s own isolated database (business users + catalogue data). First-time setup creates the schema for each, then registers a sample tenant.
# 1. Start PostgreSQL (creates central + sample tenant + template databases)docker compose up -ddocker compose ps # wait for STATUS = healthy
# 2. Create the central schema (admin identity + Tenants registry)dotnet ef database update -c CentralDbContext -p src/NexisOmni.Infrastructure -s src/NexisOmni.Infrastructure
# 3. Create the sample tenant's schema (business identity + Branch/Product)$env:TENANT_DB_CONNECTION = 'Host=localhost;Database=nexisomni_tenant_acme;Username=postgres;Password=postgres'dotnet ef database update -c TenantDbContext -p src/NexisOmni.Infrastructure -s src/NexisOmni.InfrastructureRemove-Item Env:\TENANT_DB_CONNECTIONAfter the schemas exist, register the sample tenant row in the central database (its ConnectionString points at that tenant’s isolated database). The full INSERT is in the developer guide, step 4. The seeded sample tenant is:
| Thing | Value |
|---|---|
| Sample tenant id | 11111111-1111-1111-1111-111111111111 |
| Sample tenant database | nexisomni_tenant_acme |
| Demo business user | demo@acme.test / Demo123$ (created already-confirmed) |
Build and run
Section titled “Build and run”dotnet build NexisOmni.slnx # build everythingdotnet run --project src/NexisOmni.Api --launch-profile http # API on http://localhost:5089The http profile keeps curl simple and listens on http://localhost:5089. The https profile listens on https://localhost:7298 and redirects plain HTTP to it.
Once the API is up, the two auth planes behave differently on the wire:
- Admin plane (
/admin/*) sends an admin bearer token and never aTenant-IDheader. - Tenant plane (
/identity/*and other business routes) sends a tenant bearer token plus aTenant-ID: <guid>header on every request, including login.
Money crosses the wire as a quoted decimal string (for example "118.00"), never a number; enum values are PascalCase strings. See API contract for the binding rules.
Run the tests
Section titled “Run the tests”There is one xUnit test project per production project under tests/. The suite (~566 tests, spanning all feature modules) runs in two tiers:
dotnet test NexisOmni.slnx- EF Core InMemory for logic that does not need a real database - domain rules, service status flows, request validation, and tenant routing.
- Real PostgreSQL via Testcontainers for behaviour the InMemory provider cannot model, such as partial unique indexes, cascade deletes, and genuine optimistic-concurrency conflicts.
The PostgreSQL-backed tests use Xunit.SkippableFact and skip automatically when Docker is unavailable rather than failing, so the suite stays green on a runner with no Docker daemon.
Run a single test by name:
dotnet test --filter "FullyQualifiedName~<substring>"Where to go next
Section titled “Where to go next”- Full backend setup, everyday tasks, and gotchas:
NexisOmni/docs/developer-guide.md(canonical source). - The wire contract that binds the backend to the web clients: API contract.
- Design decisions: the ADRs under
NexisOmni/docs/adr/(for example0014-auth-and-secret-hardening.mdfor signing and encryption,0016-enterprise-security-model.mdfor permissions).