Local Development & Workflow
Roja's local development environment is designed to be fast, reproducible, and container-free. Local data services (PostgreSQL 18 and Redis 8) and backend hot reloading run natively on your host machine via Nix flakes and process-compose, avoiding Docker virtualization overhead.
Prerequisitesβ
Before starting, install the required toolchains:
| Tool | Version | Purpose |
|---|---|---|
| Go | 1.27+ | Backend API and worker compilation |
| Bun | 1.3.14+ | Monorepo package manager and runtime |
| Node.js | v24 (see .nvmrc) | Node ecosystem runtime compatibility |
| Nix | Flakes enabled | Native Postgres/Redis process runner & container image builder |
| Atlas CLI | Latest | Declarative database migration engine |
| sqlc | Latest | Compiles SQL queries into type-safe Go code |
| Air | Latest | Live reload daemon for Go backend (go tool air) |
Docker is optional: You do not need Docker to run Roja locally. Docker is only needed if you want to execute isolated database integration test suites (task backend:test-integration) or test Nix-built container images locally.
Step-by-Step Initial Setupβ
Clone the monorepo and install Bun workspace packages from the repo root:
git clone https://github.com/getroja/roja.git
cd roja
bun install
Copy the example environment template in backend/:
cp backend/.env.example backend/.env
The default connection strings for native local development are:
DATABASE_URL=postgres://roja_user:roja_password@127.0.0.1:5432/roja?sslmode=disable
REDIS_URL=redis://:roja_redis_password@127.0.0.1:6379/0
PORT=8080
JWT_SECRET=local-dev-jwt-secret-do-not-use-in-production
Start native PostgreSQL 18 and Redis 8 via Nix from the repo root:
task services # Equivalent to: nix run .#services
This launches both daemons natively via process-compose. Data persists across restarts in ./data/. Press Ctrl+C to cleanly shut down.
With the services running, initialize the schema and generate Go & TypeScript clients:
task apply-db # Runs Atlas migration apply against local database
task generate # Compiles sqlc queries and OpenAPI specs into Go code
Load default lookup tables, insurance providers, and verified test accounts:
make -C backend seed-prod
make -C backend seed-dev
Seeded accounts and test fixtures are defined in backend/db/seed.dev.sql. Test account credentials and role assignments for local simulation can be inspected in that seed fixture or reset using local dev tooling:
- Borrower account (Verified borrower with bank account linked)
- Lender account (Verified lender with active wallet)
- Staff account (Staff account with operational review access)
Running the Applicationβ
Roja supports running individual components or the full multi-tier platform simultaneously.
Common Run Modesβ
# Option 1: Full-stack dev (starts services, API hot reload, workers, Web :3000, Staff :3001, Expo Metro :8081)
task full-stack # = nix run .#full-stack
# Option 2: Services + Backend API hot-reload
task dev-stack # = nix run .#dev
# Option 3: Modular execution (Run services in one terminal, app in another)
task services # Terminal 1: Postgres + Redis
task api # Terminal 2: Go API with Air (:8080)
bun --filter roja-web dev # Terminal 3: Customer Web (:3000)
bun --filter roja-staff dev -- -p 3001 # Terminal 4: Staff Dashboard (:3001)
task app # Terminal 5: Mobile Expo Metro (:8081)
Health & Diagnostic Endpointsβ
Verify backend connectivity:
GET http://localhost:8080/healthzβ Basic liveness probe.GET http://localhost:8080/readyzβ Readiness probe reporting PostgreSQL and Redis connectivity.GET http://localhost:8080/v1/meta/versionβ Returns API version and commit hash.
The backend has no root / route; root returns 404 Not Found. All functional endpoints live under /v1/....
Database Management & Migrationsβ
Roja uses a declarative schema with versioned migrations powered by Atlas and sqlc.
backend/db/schema/*.sql ββ(atlas diff)βββΊ backend/db/migrations/*.sql
β β
β (atlas apply) β (atlas apply)
βΌ βΌ
Postgres Database βββββββββββββββββββββββββββββββββββββ
β²
β (sqlc generate)
backend/db/query/*.sql ββββββββββββββββββΊ backend/db/gen/*.go
Workflow for Database Changesβ
Always modify the declarative schema files in backend/db/schema/ (e.g. 02_users.sql, 07_transactions.sql, etc.). Never write manual SQL migration files directly without diffing.
Generate a new versioned migration from your schema changes:
task backend:migrate-diff NAME=add_borrower_fields
Atlas compares backend/db/schema/ against the migration baseline and writes a new file in backend/db/migrations/ along with an updated atlas.sum hash.
Apply the new migration to your local database:
task apply-db # = task backend:apply-db
If your change requires new or updated database queries, add them to backend/db/query/*.sql, then regenerate Go models:
task generate # = task backend:generate
Zero-Downtime & Concurrent Index Rulesβ
In production, adding an index to a busy financial table using standard CREATE INDEX locks writes. You must follow the Concurrent Index Rule:
- Hand-edit the generated migration file in
backend/db/migrations/. - Add
-- atlas:txmode noneat the top of the file so Atlas executes statements outside a transaction block. - Change
CREATE INDEXtoCREATE INDEX CONCURRENTLY. - Re-calculate the migration directory integrity hash:
task backend:migrate-hash
task backend:apply-db and CI migration linters will strictly refuse to apply migrations if atlas.sum does not match the file hashes in backend/db/migrations/.
CI Migration Linterβ
CI executes task backend:migrate-lint-oss on pull requests to detect destructive schema modifications (dropping columns, dropping tables, or locking alterations). If an intentional destructive change is needed, annotate the SQL line with:
-- lint:ignore <reason for intentional data loss or column removal>
ALTER TABLE users DROP COLUMN legacy_status;
Code Quality & Toolingβ
Native TypeScript 7 Typecheckβ
Every workspace extends tools/tsconfig/base.json and runs stable native TypeScript 7:
# Typecheck specific workspace
bun --filter roja-web typecheck
bun --filter roja-staff typecheck
bun --filter @roja/common typecheck
bun --filter @roja/docs typecheck
# Check all workspaces
bun run check
Oxc Linter and Formatterβ
Roja uses the high-performance Rust-based Oxc toolchain (oxlint and oxfmt) instead of legacy ESLint:
bun run lint # Runs oxlint across all workspaces
bun run format # Formats JS/TS/JSX/JSON/CSS using oxfmt
Prettier is reserved solely for formatting Markdown, YAML, and SCSS stylesheets.
Git Worktree Conventionsβ
When working on isolated features, bugfixes, or PR reviews using Git worktrees, follow these repository rules:
- Dedicated Directory Root: Create worktrees under
~/code/worktrees/<repo>/<slug>, never inside the repository (e.g..worktrees/insideroja/is forbidden). - Shared node_modules: Worktrees share the primary checkout's
node_modulesto save disk space and eliminate repetitivebun installruns. - Workspace Linking: If TypeScript compiler reports
TS2305: has no exported memberfor an export in@roja/commonor@roja/client, the Bun workspace symlinks need refreshing in the worktree. Run:
bash .claude/hooks/link-workspace-packages.sh
Next Stepsβ
Understand the double-entry audit mirror, chart of accounts, and ledger reconciliation.
Domain Models & LifecyclesDeep-dive into tickets, loans, KYC verification, scoring, and collections.
Frontend ArchitectureExplore the Web, Staff, and Mobile architectures and shared packages.
Async Workers & OperationsLearn how Asynq workers, outbox events, pprof diagnostics, and Flux GitOps operate.