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:
| Project | Purpose |
|---|---|
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-client | Generated TypeScript client, shared by App and Site |
API conventions
- Layering:
Service→Business→Data, 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
Guidproperty, field, or parameter is suffixed_UID. Global usings live in each project'sUsings.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/.tsxas 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 buildMaking 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-changespasses - this is exactly what CI checks.