Adding Drizzle and Turso to the Epic Stack
Kent C. Dodds’ Epic Stack is a sample boilerplate for an opinionated full-stack Remix app. I like it: I’ve used it both as a reference implementation (while building Jobfolio) and as a template (for the Covenant Foundry website). One of the interesting parts of the stack is that it uses a distributed SQLite database via Fly.io’s LiteFS. This makes reads extremely fast, at the cost of limiting write speed.
Fly.io’s managed service, LiteFS Cloud has been deprecated, however. I decided to explore swapping in Turso instead.
Swapping Turso for LiteFS
The libsql
client driver can connect to a SQLite db remotely via HTTP (Turso’s hosted solution). This enables embedded replicas, where an SQLite database can be copied locally (within a Fly.io VM, say) and writes can be replicated to the hosted SQLite db.
Swapping Drizzle for Prisma
Prisma’s support for Turso is currently (December 2024) Early Access. Prisma Migrate works for local SQLite databases, replicated via file system (e.g. LiteFS), but isn’t supported over Turso’s HTTP connection.
Drizzle has more complete support for Turso, including migrations.
Migrating the Epic Stack
The first step was converting the Prisma schema to a Drizzle schema and setting up migrations. Between drizzle-kit pull
to extract the schema from the existing database and Cursor to help set up the relationships, this part was pretty easy.
Drizzle has an object syntax for querying that is similar to Prisma’s, but inserts & updates have to be handled with its SQL querybuilder syntax. I went through and rewrote the database queries with Drizzle instead. This was tedious, but Cursor was able to get most of the way there with a little help.
Once I got the end-to-end tests running, I discovered there was a problem. The e2e tests ran many queries in quick succession, and some started failing randomly with SQLITE_BUSY errors. I tried both of Drizzle’s SQLite drivers (libsql and better-sqlite3), and though the errors were different, neither solved the flakiness.
Drizzle does work fine with the Turso dev server, either connecting directly or with an embedded replica. This suggests the problem is something to do with write contention against a local database. (With an embedded replica, the tests should be reading from the embedded sqlite file, but writing via the sync URL to the dev server).
And… searching for answers didn’t help, so that’s where I’m calling it for this experiment!
Conclusions
Covenant Foundry is currently running the Drizzle + Turso combination (without embedded replicas - yet). I’m running a Turso dev server for local development and for end-to-end tests in the build pipeline. This has been working well.
I started a fork of the Epic Stack to demonstrate the Turso setup. The pipeline build works, though I haven’t migrated the secondary cache database from LiteFS, and I haven’t tested deploying it to Fly.io. Use it for inspiration in your own setup!