SQLite
Switch the project from PostgreSQL to SQLite.
For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: text/markdown.
TurboStarter ships with PostgreSQL by default, but you can absolutely run it on SQLite if that fits your product better.
SQLite is a good option when you want a simpler local setup, a single file database for development, or a libSQL provider such as Turso in production. The important thing to understand is that SQLite is a customization rather than a one-line toggle.
PostgreSQL is still the default
At the time of writing, the starter is wired to PostgreSQL in packages/db/src/server.ts, packages/db/drizzle.config.ts, packages/auth/src/server.ts, and the schema files in packages/db/src/schema.
When SQLite makes sense
SQLite is a strong fit when you want:
- local development without a database container
- a simpler deployment story for smaller products
- an edge-friendly database provider such as Turso/libSQL
You should usually stay on PostgreSQL if you need:
- heavier concurrent write traffic
- PostgreSQL-specific features
- a fully drop-in experience with the starter's default schema and migrations
What you need to change
Moving to SQLite usually means updating these areas:
packages/db/package.json- add a SQLite driverpackages/db/src/env.ts- validate the new connection settingspackages/db/src/server.ts- initialize Drizzle with a SQLite clientpackages/db/drizzle.config.ts- switch Drizzle Kit frompostgresqltosqlitepackages/auth/src/server.ts- change Better Auth fromprovider: "pg"toprovider: "sqlite"packages/db/src/schema/*- replace PostgreSQL-only schema utilities with SQLite-compatible onespackages/db/src/utils/index.ts- review helper types that still import PostgreSQL table types
The good news is that not everything needs to be rewritten. For example, helper utilities such as buildConflictUpdateColumns already support both PgTable and SQLiteTable.
Install the SQLite driver
For this setup, the most practical choice is libSQL, because the same driver works with both local SQLite files and remote Turso databases.
pnpm --filter @workspace/db add @libsql/client
pnpm --filter @workspace/db remove postgresUpdate environment variables
For local development, point DATABASE_URL to a SQLite file. Drizzle ORM's libSQL tooling expects the file: prefix.
DATABASE_URL="file:./.data/local.db"If you use Turso, set the remote URL and auth token instead:
DATABASE_URL="libsql://your-database.turso.io"
DATABASE_AUTH_TOKEN="your-token"Ignore local database files
If you keep a local SQLite file inside the repository, add its directory to .gitignore so you don't commit the database by accident. A common choice is .data/.
Replace the database client
The current database package uses postgres-js. Swap it to the libSQL client in packages/db/src/server.ts.
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { env } from "./env";
import * as schema from "./schema";
const client = createClient({
url: env.DATABASE_URL,
authToken: env.DATABASE_AUTH_TOKEN,
});
export const db = drizzle(client, {
schema,
casing: "snake_case",
});You should also relax the env validation in packages/db/src/env.ts so it accepts both local file: URLs and remote libsql:// URLs:
export const preset = {
id: "db",
server: {
DATABASE_URL: z.string().min(1),
DATABASE_AUTH_TOKEN: z.string().optional(),
},
} as const;Switch Drizzle Kit to SQLite
Update packages/db/drizzle.config.ts to use the Drizzle Kit SQLite dialect:
import { defineConfig } from "drizzle-kit";
import { env } from "./src/env";
export default defineConfig({
out: "./migrations",
schema: "./src/schema/index.ts",
dialect: "sqlite",
casing: "snake_case",
dbCredentials: {
url: env.DATABASE_URL,
authToken: env.DATABASE_AUTH_TOKEN,
},
});Update Better Auth
The auth package is also PostgreSQL-first today. In packages/auth/src/server.ts, change the Better Auth Drizzle adapter provider:
database: drizzleAdapter(db, {
provider: "sqlite",
schema,
}),This is the key Better Auth change when you keep using the Drizzle adapter.
Convert the schema to SQLite
This is the largest part of the migration.
Today the starter's schema uses drizzle-orm/pg-core utilities such as pgTable and pgEnum in packages/db/src/schema/auth.ts and packages/db/src/schema/billing.ts. SQLite uses drizzle-orm/sqlite-core instead, so you need to review both files carefully.
The most common replacements are:
pgTable->sqliteTablepgEnum(...)->text(..., { enum: [...] })- PostgreSQL-typed helpers such as
PgTable/PgTableWithColumns-> SQLite-compatible or dialect-neutral equivalents - PostgreSQL-specific raw SQL such as
excluded.column_name-> keep only where the target dialect supports it
For example, enum-like fields from packages/db/src/schema/billing.ts can be modeled as text columns:
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
const subscriptionStatuses = [
"active",
"canceled",
"incomplete",
"incomplete_expired",
"past_due",
"paused",
"trialing",
"unpaid",
] as const;
export const subscription = sqliteTable("subscription", {
id: text("id").primaryKey(),
status: text("status", { enum: subscriptionStatuses }).notNull(),
});The buildConflictUpdateColumns helper is already partly prepared for SQLite because it accepts SQLiteTable, but getOrderByFromSort still imports PostgreSQL-only types:
import type { SQLiteTable } from "drizzle-orm/sqlite-core";
// replace PgTable / PgTableWithColumns imports as neededReview PostgreSQL-only columns carefully
The largest schema changes are the pgEnum definitions in packages/db/src/schema/billing.ts and the PostgreSQL-only helper types in packages/db/src/utils/index.ts. This is not a blind search-and-replace migration.
Regenerate migrations from scratch
Once the schema and config are updated, generate a fresh SQLite migration set.
Because the existing migration history was generated for PostgreSQL, don't reuse it as-is for SQLite.
rm -rf packages/db/migrations
pnpm with-env turbo db:generate
pnpm with-env pnpm --filter @workspace/db db:migrateAfter that, you can keep using the same Drizzle workflows:
pnpm with-env turbo db:generatepnpm with-env pnpm --filter @workspace/db db:migratepnpm with-env pnpm --filter @workspace/db db:studio
Recommended migration order
If you're converting an existing project, this is the safest order:
- Commit your PostgreSQL version first.
- Switch the client, Drizzle config, and Better Auth provider.
- Convert the schema files from
pg-coretosqlite-core. - Update
packages/db/src/utils/index.tsfor SQLite-compatible typing where needed. - Delete old PostgreSQL migrations.
- Generate fresh SQLite migrations.
- Smoke test sign-in, sign-up, billing, and organization flows.
Final notes
SQLite works well with the starter, but it is not currently the "default path". Treat it as an intentional database adapter swap, not just a connection string change.
If you want the smoothest setup, prefer @libsql/client, keep the schema conservative, and regenerate your migrations cleanly once the dialect switch is complete.
How is this guide?
Last updated on