# TrakRF Platform
RFID/BLE asset tracking platform for manufacturing and logistics.
## Architecture
**Stack:**
- Backend: Go
- Frontend: React
- Database: TimescaleDB
- MQTT: Integrated broker
- Deployment: Docker
**Structure:**platform/ ├── backend/ # Go API server (placeholder) ├── frontend/ # React web app (@trakrf/frontend - handheld RFID app) ├── database/ │ └── migrations/ # Versioned SQL migrations (golang-migrate) ├── docs/ # Documentation │ └── frontend/ # Frontend docs (architecture, vendor specs) ├── marketing/ # Marketing website └── docker-compose.yml
**Frontend**: Full-featured React + TypeScript app for CS108 RFID handheld readers with Web Bluetooth integration, comprehensive test suite (Vitest + Playwright), and mock testing support. See [docs/frontend/](docs/frontend/) for architecture and vendor specs.
## Development
### Prerequisites
- Docker & Docker Compose
- Just (task runner) - https://just.systems/
- direnv (optional but recommended - auto-loads `.env.local`)
**Note:** Go and Node.js are NOT required for Docker-based development. Install them only if you want to run services natively.
### First step in any fresh checkout or worktree
```bash
just bootstrap
The backend embeds two generated, gitignored artifacts — backend/frontend/dist
and the OpenAPI specs under backend/internal/handlers/swaggerspec/ — so a
fresh tree does not compile until they exist. Without this step just validate
fails with pattern frontend/dist: no matching files found, which names neither
the missing step nor the fix.
just bootstrap installs workspace dependencies, builds the frontend and
generates the specs. It is idempotent and near-instant once satisfied, so it is
safe to re-run whenever; just bootstrap --force rebuilds regardless. It fails
loudly and non-zero — if it did not, the next command to fail would be an
unrelated-looking one.
Cold, it takes a couple of minutes and needs no supervision, so it is worth backgrounding at the start of a session rather than waiting on it.
1. Configure environment
# Optional. Every value has a working local default, so the stack comes up on
# the canonical database with no env file at all — you need this only to set
# things that have no sensible default (MQTT credentials, a Resend key).
cp .env.local.example .env.local
# Enable direnv (auto-loads .env.local)
direnv allowThere is one local env file: .env.local. just bootstrap symlinks .env
to it, because docker compose reads .env while direnv reads .env.local — as
two real files they can name two different databases, and the shell environment
silently outranks .env, so the disagreement does not even present the same way
twice. That is not hypothetical: it is what TRA-1190 was filed for.
It holds parts, not connection URLs — PG_DATABASE, PG_APP_USER,
PG_MIGRATE_USER and their passwords — and docker-compose and the justfiles
assemble the DSNs from them. This mirrors what the cluster has done since the
move to GKE/CNPG, where helm/trakrf-backend interpolates
postgresql://$(PGUSER):$(PGPASSWORD)@host:port/db from a role name and a
Secret rather than storing a URL. Nothing stores a DSN, so nothing can hold a
stale one. just test-env-drift checks these against database/justfile, which
is what actually declares the database and role names.
Database roles. The local stack mirrors what preview and prod run on: a
trakrf database owned by trakrf-migrate (DDL), served by trakrf-app
(CRUD only — not a superuser, no BYPASSRLS, owns nothing). just database up
creates both. This is deliberate: a superuser bypasses row-level security, so
connecting as postgres means no policy is ever evaluated locally and a missing
WithOrgTx looks healthy until it reaches a deployed environment. If you hit a
permission error, that is the signal working — don't put postgres back in
PG_APP_USER. See database/sql/ and ADR 0003.
2. Start full stack
# Start database + backend with hot-reload
# This will:
# 1. Start TimescaleDB and bootstrap the trakrf database + roles
# 2. Run database migrations automatically (as trakrf-migrate)
# 3. Start backend with Air hot-reload (as trakrf-app)
just dev
# Backend will be available at http://localhost:8080
# Logs are streaming to terminal
# In another terminal, test endpoints:
curl localhost:8080/healthz # Liveness check
curl localhost:8080/readyz # Readiness check
curl localhost:8080/health # Detailed health (JSON)3. Develop with hot-reload
# Edit backend/main.go or backend/health.go
# Air automatically rebuilds and restarts (< 5 seconds)
# View logs
just dev-logs4. Stop services
# Stop all services
just dev-stop
# Or stop database
just database down # New delegation syntax
just db down # Lazy alias (same result)This project uses Just's delegation pattern for monorepo task management.
From project root (delegation syntax):
# Full stack
just dev # Docker-based (db + backend container + migrations)
# Workspace-specific (delegation)
just frontend dev # Start Vite dev server
just backend dev # Start Go server locally
just frontend typecheck # TypeScript type checking
just backend test # Run Go tests
# Lazy dev aliases (shorter syntax)
just fe dev # Same as: just frontend dev
just be test # Same as: just backend test
just db up # Same as: just database up
# Combined validation
just lint # Lint both workspaces
just test # Test both workspaces
just build # Build both workspaces
just validate # Full validation (lint + test + build)From workspace directories (direct commands):
# Backend development
cd backend
just dev # Start Go server locally
just test # Run backend tests
just validate # Backend-only validation
just migrate # Run database migrations
# Frontend development
cd frontend
just dev # Start Vite dev server
just test # Run frontend tests
just typecheck # TypeScript checking
just validate # Frontend-only validation
# Database operations
cd database
just up # Start TimescaleDB
just logs # View database logs
just psql # Connect to psqlHow it works:
- Delegation:
just <workspace> <command>from root →cd <workspace> && just <command> - Lazy aliases:
just db,just fe,just befor shorter commands - Fallback: Workspace justfiles can call root recipes
- Context-aware:
just devdoes the right thing based on current directory
Full Stack:
just dev # Start database + backend
just dev-stop # Stop all services
just dev-logs # Follow logs (all services)Database (infrastructure commands):
just database up # Start TimescaleDB
just database down # Stop TimescaleDB
just database logs # View database logs
just database psql # Connect to psql (or: just database shell)
just database status # Check database health
just database reset # ⚠️ Reset database (deletes all data)
# Lazy aliases (shorter):
just db up # Same as: just database up
just db logs # Same as: just database logs
just db psql # Same as: just database psqlDatabase Migrations (application commands):
just backend migrate # Apply all pending migrations
just backend migrate-down # Rollback last migration
just backend migrate-status # Show current migration version
just backend migrate-create foo # Create new migration (000XXX_foo.up/down.sql)
just backend migrate-force 5 # Force version to 5 (recovery only)
# Lazy aliases (shorter):
just be migrate # Same as: just backend migrate
just be migrate-status # Same as: just backend migrate-statusMigrations run automatically on just dev startup. Manual migration commands are useful for:
- Production deployments
- Testing migration rollback
- Creating new migrations
- Recovery from failed migrations
If you have Go 1.25+ installed, you can run backend natively:
# Run backend natively (from backend/ directory)
cd backend && just dev # Starts at localhost:8080
# Or use delegation from root
just backend dev # Starts at localhost:8080
# Run validation (from root with delegation)
just backend lint # Format + lint
just backend test # Run tests
just backend build # Build binary
just backend validate # All checksNote: Docker is the recommended workflow. Native commands are available for those who prefer it.
Run validation checks using the delegation pattern:
# Full validation (lint, test, build)
just validate
# Combined checks across all workspaces
just lint # Lint backend + frontend
just test # Test backend + frontend
just build # Build backend + frontend
# Backend validation (delegation from root)
just backend validate # All backend checks
just backend lint # go fmt + go vet
just backend test # go test
just backend build # go build
# Frontend validation (delegation from root)
just frontend validate # All frontend checks
just frontend lint # ESLint
just frontend typecheck # TypeScript
just frontend test # Vitest unit tests
just frontend build # Vite production build
# Workspace-specific (from workspace directory)
cd backend && just validate # Backend-only validation
cd frontend && just validate # Frontend-only validationSee justfile for all available commands.
- JWT authentication
- Asset management (CRUD)
- Location tracking via RFID/BLE
- MQTT ingestion pipeline
- Real-time asset location queries
- Continuous aggregates for historical data
- REST API with key authentication
- ERP/WMS integrations
- Multi-tenant isolation
- Self-hosted deployment options
# Single container deployment
docker build -t trakrf/platform .
railway up# Customer deployment via docker-compose
docker-compose -f docker-compose.prod.yml upSee load-tests/README.md for the k6 capacity probe — how to run it, current baselines, and what is (and isn't) measured.
Business Source License (BSL) - see LICENSE
Phase 4A - Foundation + User Management (v1)
The backend exposes RESTful JSON APIs under /api/v1:
GET /healthz # K8s liveness probe
GET /readyz # K8s readiness probe (with DB check)
GET /health # Human-friendly health status (JSON)GET /api/v1/accounts # List all accounts (paginated)
GET /api/v1/accounts/:id # Get account by ID
POST /api/v1/accounts # Create new account
PUT /api/v1/accounts/:id # Update account
DELETE /api/v1/accounts/:id # Soft delete accountCross-org back office. Every route requires is_superadmin; any other session
gets 403. Self-service profile edits go through PATCH /api/v1/users/me.
No POST: removed in TRA-1103 — users are created by signup and org invitations.
GET /api/v1/users # List all users (paginated)
GET /api/v1/users/:id # Get user by ID
PUT /api/v1/users/:id # Update user
DELETE /api/v1/users/:id # Soft delete userGET /api/v1/accounts/:account_id/users # List users in account
POST /api/v1/accounts/:account_id/users # Add user to account
PUT /api/v1/accounts/:account_id/users/:user_id # Update user role/status
DELETE /api/v1/accounts/:account_id/users/:user_id # Remove user from accountResponse Format:
- Success:
{"data": {...}}or{"data": [...], "pagination": {...}} - Error: RFC 7807 Problem Details with request ID for tracing
Pagination: ?page=1&per_page=20 (default: page 1, 20 items, max 100)
Full API documentation coming soon at /api/docs.
See docs/releasing.md for the production release runbook — tag-before-build, promotion, the migration-ledger relocation ordering, post-deploy verification and rollback.
See CONTRIBUTING.md for detailed guidelines on:
- Development setup and workflow
- Code style and architecture patterns
- Testing requirements
- Pull request process
- Commit message conventions