Skip to content

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.

  • .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:
Terminal window
dotnet tool restore

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.

Terminal window
# 1. Start PostgreSQL (creates central + sample tenant + template databases)
docker compose up -d
docker 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.Infrastructure
Remove-Item Env:\TENANT_DB_CONNECTION

After 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)
Terminal window
dotnet build NexisOmni.slnx # build everything
dotnet run --project src/NexisOmni.Api --launch-profile http # API on http://localhost:5089

The 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 a Tenant-ID header.
  • Tenant plane (/identity/* and other business routes) sends a tenant bearer token plus a Tenant-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.

There is one xUnit test project per production project under tests/. The suite (~566 tests, spanning all feature modules) runs in two tiers:

Terminal window
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:

Terminal window
dotnet test --filter "FullyQualifiedName~<substring>"
  • 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 example 0014-auth-and-secret-hardening.md for signing and encryption, 0016-enterprise-security-model.md for permissions).