Database & Migrations
How FangDash stores data in Cloudflare D1 with Drizzle — the full 9-table schema, running and generating migrations, plus backup and restore.
FangDash stores all persistent data in Cloudflare D1 (SQLite). The ORM is Drizzle, and
migrations are SQL files in apps/api/drizzle/.
Overview
The schema is defined in apps/api/src/db/schema.ts and has 9 tables — four from Better Auth and five for the game.
| Table | Purpose |
|---|---|
user | Accounts from Better Auth (username, avatar, Twitch ID, email) |
session | Active login sessions |
account | OAuth provider links (Twitch) |
verification | Better Auth verification tokens |
player | Game stats, XP, level, equipped skin |
score | Individual game scores |
player_skin | Unlocked skins per player |
player_achievement | Unlocked achievements per player |
race_history | Multiplayer race results |
The first four tables are managed by Better Auth; the last five hold your gameplay data. Every score, unlock, and race result links back to a player, which links to a user.
Local vs remote
D1 runs in two modes, and every migration command takes a flag to pick one:
- Local — an SQLite file on your machine, used by
bun run dev. Target it with--local. - Remote — the production D1 database on Cloudflare. Target it with
--remote.
Always test against --local before you touch --remote.
Running migrations
Apply existing migrations
# Local development
bunx wrangler d1 migrations apply fangdash-db --local
# Production
bunx wrangler d1 migrations apply fangdash-db --remoteGenerate a migration from schema changes
After you edit apps/api/src/db/schema.ts:
cd apps/api
bunx drizzle-kit generateThis writes a new SQL migration file to apps/api/drizzle/. Review it, then apply it locally first and remotely once you're happy:
bunx wrangler d1 migrations apply fangdash-db --local # test locally first
bunx wrangler d1 migrations apply fangdash-db --remote # then deployBackup and restore
Export (backup)
# Export the remote database to a SQL file
bunx wrangler d1 export fangdash-db --remote --output backup.sqlRestore
# Import a SQL backup into the local database
bunx wrangler d1 execute fangdash-db --local --file backup.sqlAlways test migrations locally before applying them to production. D1 migrations have no automatic rollback, so a backup is your safety net.
Troubleshooting
"table not found" errors
Migrations haven't been applied. Run:
bunx wrangler d1 migrations apply fangdash-db --remoteMigration fails with "already exists"
The migration was partially applied. Check which migrations have run:
bunx wrangler d1 migrations list fangdash-db --remoteIf a migration is stuck, you may need to manually mark it as applied or fix the conflicting state in the database.
Schema drift between local and remote
If your local database has different tables than remote:
- Drop and recreate local: delete
.wrangler/state/in the api directory. - Re-apply all migrations:
bunx wrangler d1 migrations apply fangdash-db --local.
UNIQUE constraint violations
These usually mean a race condition in player creation (handled automatically) or duplicate data. Check the specific table and constraint in the error message:
bunx wrangler d1 execute fangdash-db --remote \
--command "SELECT * FROM player WHERE user_id = 'USER_ID';"Database is locked / timeout errors
D1 has concurrency limits. If you see timeout errors under load:
- Make sure you're not running expensive queries (full table scans).
- Check that indexes exist for frequently queried columns.
- Batch writes where possible — FangDash already does this for race results.
Admin Panel
Run your FangDash instance from the admin panel — view dashboard stats, ban or unban players, delete scores with rollback, and promote admins via CLI
Common Errors & Fixes
Troubleshoot common FangDash errors — Twitch OAuth failures, API 401/403/429 rate limits, D1 database issues, PartyKit WebSocket drops, and build failures.