Prerequisites: Node.js ≥ 24, npm, Redis
git clone https://github.com/fieldtrack-tech/api.git
cd api
npm install
cp .env.example .env
# Edit .env — fill in Supabase URL, keys, Redis URL, ALLOWED_ORIGINS| Purpose | Pattern | Example |
|---|---|---|
| New feature | feature/<description> |
feature/expense-attachments |
| Bug fix | fix/<description> |
fix/session-double-close |
| Documentation | docs/<description> |
docs/update-api-reference |
| Tests | test/<description> |
test/analytics-edge-cases |
| Chores / deps | chore/<description> |
chore/bump-fastify-5 |
All PRs target master. master is protected — CI must pass before merge.
type(scope): short imperative description
Allowed types:
| Type | When to use |
|---|---|
feat |
New user-facing functionality |
fix |
Bug fix |
refactor |
Internal code change — no behaviour change |
ci |
CI/CD workflow changes |
infra |
Infrastructure, Docker, nginx, monitoring |
docs |
Documentation only |
test |
Adding or updating tests |
chore |
Dependency bumps, tooling, housekeeping |
Scope should be the module or layer affected (e.g. auth, expenses, analytics, deploy, worker, rls).
Examples:
feat(expenses): add bulk-approve endpoint for admins
fix(auth): handle missing organization_id claim in JWT
refactor(analytics): replace read-then-upsert with atomic RPC calls
ci(deploy): add SARIF-based Trivy gate before image push
infra(nginx): block /docs and /openapi.json in production
docs(deployment): document blue-green rollback procedure
test(attendance): cover double-checkout edge case
chore(deps): bump @fastify/jwt to 9.1.0
-
Branch off
master:git checkout -b feature/your-feature-name
-
Make your changes following the existing code conventions (TypeScript strict mode, ESM imports, Fastify patterns).
-
Run the test suite and type-checker before committing:
npm run typecheck npm test -
Commit with a conventional commit message (see format above).
-
Push your branch and open a pull request against
master.
Every domain module follows the same four-layer pattern:
src/modules/<domain>/
├── <domain>.schema.ts — Zod schemas and TypeScript types
├── <domain>.repository.ts — Supabase queries, always scoped by organization_id
├── <domain>.service.ts — Business logic, throws domain errors
├── <domain>.controller.ts — HTTP parsing, calls service, sends response via handleError
└── <domain>.routes.ts — Route registration, middleware, rate-limit config
- Never skip tenant scoping. Every repository query must include
.eq("organization_id", organizationId). - All errors must flow through
handleError(). Do not callreply.status().send()directly in service or repository code. - Domain errors belong in
src/utils/errors.ts. Use specific error classes (NotFoundError,ForbiddenError, etc.) rather than generic ones. - Use
TenantContextwhen writing service-layer code that runs outside an HTTP request (e.g. workers). - All new endpoints need a rate limit. Check existing routes for examples of per-user (JWT
sub) rate limiting.
- Unit tests go in
tests/unit/ - Integration tests go in
tests/integration/ - Use
vi.mock()at the top of integration test files to mock repository dependencies - Use
TEST_UUID()fromtests/helpers/uuid.tsfor all UUID values in tests (Zod 4 enforces strict RFC-4122 validation) - Do not write tests that require a live Supabase project — mock at the repository level
- Ensure
npx tsc --noEmitexits0 - Ensure
npm run testpasses all 124+ tests - Write tests for new behaviour — PRs without tests for new endpoints will not be merged
- Update docs/API_REFERENCE.md if you add or change any endpoint
- Update CHANGELOG.md with a short entry under the appropriate section
Use GitHub Issues. Include:
- Node.js version (
node --version) - What you expected to happen
- What actually happened
- Minimal reproduction steps
For security vulnerabilities, see SECURITY.md instead.