Skip to content
FangDash
Getting Started
Data layer

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.

TablePurpose
userAccounts from Better Auth (username, avatar, Twitch ID, email)
sessionActive login sessions
accountOAuth provider links (Twitch)
verificationBetter Auth verification tokens
playerGame stats, XP, level, equipped skin
scoreIndividual game scores
player_skinUnlocked skins per player
player_achievementUnlocked achievements per player
race_historyMultiplayer 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 --remote

Generate a migration from schema changes

After you edit apps/api/src/db/schema.ts:

cd apps/api
bunx drizzle-kit generate

This 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 deploy

Backup and restore

Export (backup)

# Export the remote database to a SQL file
bunx wrangler d1 export fangdash-db --remote --output backup.sql

Restore

# Import a SQL backup into the local database
bunx wrangler d1 execute fangdash-db --local --file backup.sql

Always 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 --remote

Migration fails with "already exists"

The migration was partially applied. Check which migrations have run:

bunx wrangler d1 migrations list fangdash-db --remote

If 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:

  1. Drop and recreate local: delete .wrangler/state/ in the api directory.
  2. 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.

On this page