Install tools using mise:
mise install- Go — Backend server and worker.
- Node.js — Required as a runtime for React Native / Expo.
- Bun — JavaScript package manager used for mobile app and docs tooling.
- Task — Task runner used for building, testing, formatting, and other development workflows.
# First-time setup
task setup
# Start backend (Terminal 1)
task api:serve
# Start mobile app (Terminal 2)
task mobile:servePress i for iOS simulator, a for Android, or scan QR with Expo Go.
freebies/
├── apps/
│ └── mobile/ # React Native (Expo) app
├── services/
│ ├── api/ # Go API server
│ └── scheduler/ # Lightweight scheduler (CronJob container)
├── docs/ # Project-wide documentation
└── Taskfile.yml # Root task definitions
Run task --list to see all available tasks.
| Task | Description |
|---|---|
task setup |
First-time setup (install dependencies) |
task clean |
Wipe database |
task api:serve |
Start backend server |
task api:build |
Build backend binary |
task api:test |
Run backend tests |
task scheduler:build |
Build scheduler binary |
task scheduler:test |
Run scheduler tests |
task mobile:serve |
Start Expo dev server |
task docs:fmt |
Format markdown files |
See Backend Development Guide for detailed backend docs.
# Start server (auto-migrates database)
task api:serve
# Create test deals
task api:deals
# List users
./services/api/bin/freebie users list# Start Expo dev server
task mobile:serve
# Run on iOS simulator
# Press 'i' in the terminalSQLite database with automatic migrations on startup.
# Wipe and recreate database
task clean
task api:serve- Create migration in
services/api/internal/db/migrations/ - Restart server (migrations run automatically)
Example migration (007_new_offer.sql):
-- +goose Up
INSERT INTO events (id, offer_id, team_id, team_name, league, partner_name, ...)
VALUES (...);
-- +goose Down
DELETE FROM events WHERE id = '...';| Variable | Description | Default |
|---|---|---|
FREEBIE_DATABASE_PATH |
SQLite database path | freebie.db |
FREEBIE_SERVER_HOST |
Server bind address | 0.0.0.0 |
FREEBIE_SERVER_PORT |
Server port | 8080 |
Format all markdown files to 100 character width:
task docs:install # First time only
task docs:fmttask api:lint
task api:fmtAll work happens on feature branches created from main.
Use the pattern type/short-description:
| Prefix | Use for |
|---|---|
feat/ |
New features |
fix/ |
Bug fixes |
docs/ |
Documentation changes |
refactor/ |
Code restructuring (no new behavior) |
chore/ |
Tooling, deps, CI changes |
Examples: feat/add-nba-source, fix/notification-retry, docs/update-readme
When using Claude Code's /commit command while on main, a branch is automatically created using
the convention above. You don't need to create branches manually.
Follow Conventional Commits with the 50/72 rule:
- Subject line: max 50 characters, imperative mood, capitalized, no period
- Body: wrap at 72 characters, separated from subject by a blank line
- Format:
type(scope): description - Types:
feat,fix,docs,style,refactor,perf,test,chore - Scopes:
api,scheduler,mobile,docs,db - Summarize the "what" and "why", not the "how"
Try to write meaningful commit messages and avoid having too many commits on a PR. Most PRs should likely have a single commit (although for bigger PRs it may be reasonable to split it in a few). Git squash and rebase is your friend!
feat(api): Add NBA game source
Implements the Source interface for NBA live game data
using the balldontlie API.
fix(mobile): Prevent duplicate push registrations
docs: Add branching and commit conventions
See deployment guide for Kubernetes deployment instructions.
cd apps/mobile
eas build --platform ios
eas submit --platform ios