Coding Standards & Contributing

Conventions and checks to know before opening a PR.

metrd is in alpha and evolving quickly. Conventions here reflect the current state of the repo, not a fixed spec.

Repository layout

The monorepo has three deployable projects and a shared package:

ProjectPurpose
API/ASP.NET Core backend - see Tech Stack for the layer breakdown
App/The Next.js user-facing web app
Site/This documentation and marketing site
packages/api-clientGenerated TypeScript client, shared by App and Site

API conventions

  • Layering: ServiceBusinessData, one direction only. Controllers handle input/output, managers hold business logic, repositories are the only thing that touches the database.
  • Entities, not tables: repositories return entity classes, never raw EF table classes, and entities are created through a static Create() factory method.
  • Naming: any Guid property, field, or parameter is suffixed _UID. Global usings live in each project's Usings.cs, not scattered across files or in the .csproj.
  • Style: block-scoped namespaces, explicit types (no var), expression-bodied members where they fit on one line, no primary constructors.
  • Tests: MSTest with Moq. Test classes are named {ClassBeingTested}Test; test methods follow {ClassBeingTested}_{MethodBeingTested}_{ExpectedBehaviour}.
  • Most of this is enforced automatically - StyleCop ordering rules and a set of custom Roslyn analysers (Metrd.API.Analysers) run as part of the build, several with automatic code fixes available in-editor.

metrd targets light hardware (see Tech Stack), so treat database round trips as a cost, not a given. Prefer batching over per-item queries, cache reference data that rarely changes, and think about query cost in a PR the same way you'd think about correctness.

App conventions

  • Next.js App Router, TypeScript in strict mode.
  • ESLint runs against .ts/.tsx as part of CI; Prettier formats consistently (2-space indent, double quotes, trailing commas, 120-character lines).

Before opening a PR

Each project has its own CI check that runs on every pull request:

  • API: builds in Release, checks for pending EF Core migrations that weren't committed, then runs the full test suite with coverage.
  • App: installs dependencies, lints, then builds.
  • Docker: builds both images and runs a full Compose smoke test against their health endpoints - see Running with Docker.

Running the equivalent locally before pushing saves a round trip:

# API
cd API
dotnet build Metrd.API.sln
dotnet test Metrd.API.sln
 
# App
cd App
npm run lint
npm run build

Making changes

  • Keep changes scoped to the layer they belong in - a UI tweak shouldn't touch the API, and a data-model change shouldn't reach into the App.

  • If you change an API controller, hub, or contract used by the client, regenerate it:

    npm run generate-client --workspace=metrd-app
  • If you change the EF Core model, add a migration rather than editing an existing one, and make sure dotnet ef migrations has-pending-model-changes passes - this is exactly what CI checks.