KitRocket

Change Database

Switch from Supabase Postgres to self-hosted Postgres, PlanetScale, Turso, or another database.

KitRocket uses Drizzle ORM, which supports multiple databases. Switching is straightforward — update the config, change the connection string, and run migrations.

Option 1: Self-hosted Postgres

If you want to use your own Postgres instance (e.g., on Railway, Render, or a VPS):

Update connection string

DATABASE_URL="postgresql://user:password@your-host:5432/your-database"

That's it

The Drizzle config and schema already target Postgres. No code changes needed. Push the schema:

pnpm db:push

Option 2: Neon Postgres

Neon is a serverless Postgres with branching.

Update connection string

DATABASE_URL="postgresql://user:password@ep-cool-name-123.us-east-2.aws.neon.tech/neondb?sslmode=require"

Update Drizzle config (optional)

If you want to use Neon's serverless driver for better cold start performance:

pnpm add @neondatabase/serverless
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);

Push the schema:

pnpm db:push

Option 3: PlanetScale (MySQL)

PlanetScale is a serverless MySQL platform.

Install MySQL driver

pnpm add @planetscale/database drizzle-orm/mysql-core
pnpm remove @neondatabase/serverless  # if previously installed

Update Drizzle config

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema",
  out: "./src/db/migrations",
  dialect: "mysql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Update database client

import { connect } from "@planetscale/database";
import { drizzle } from "drizzle-orm/planetscale-serverless";

const connection = connect({ url: process.env.DATABASE_URL });
export const db = drizzle(connection);

Convert schema files

Drizzle schema syntax differs between Postgres and MySQL. Update each file in src/db/schema/:

// Before (Postgres):
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";

export const users = pgTable("users", { ... });

// After (MySQL):
import { mysqlTable, varchar, datetime } from "drizzle-orm/mysql-core";

export const users = mysqlTable("users", { ... });

Key differences:

PostgresMySQL
pgTablemysqlTable
textvarchar(255)
timestampdatetime
uuidvarchar(36)

Push schema

pnpm db:push

Option 4: Turso (SQLite)

Turso is edge SQLite — great for low-latency reads worldwide.

Install SQLite driver

pnpm add @libsql/client

Update connection string

DATABASE_URL="libsql://your-db-name-your-org.turso.io"
DATABASE_AUTH_TOKEN="your-turso-auth-token"

Update Drizzle config

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema",
  out: "./src/db/migrations",
  dialect: "sqlite",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
    authToken: process.env.DATABASE_AUTH_TOKEN,
  },
});

Update database client

import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";

const client = createClient({
  url: process.env.DATABASE_URL!,
  authToken: process.env.DATABASE_AUTH_TOKEN,
});

export const db = drizzle(client);

Convert schema files

// Before (Postgres):
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";

// After (SQLite):
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

export const users = sqliteTable("users", { ... });

SQLite uses integer for dates (Unix timestamps) instead of timestamp.

Push schema

pnpm db:push

After switching

  1. Update DATABASE_URL in all environments (local, Vercel, etc.)
  2. Run pnpm db:push against each database
  3. Test auth flows — Better Auth stores sessions in the database
  4. Test payment webhooks — subscriptions are stored in the database
  5. Verify all queries work (some SQL features differ between databases)

Troubleshooting

"relation does not exist"

You forgot to push the schema:

pnpm db:push

"SSL required"

Some providers require SSL. Add ?sslmode=require to your connection string.

Better Auth errors after switching

Better Auth uses the database adapter from Drizzle. If you change the database dialect, make sure the Better Auth adapter matches:

import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/lib/db";

export const auth = betterAuth({
  database: drizzleAdapter(db),
  // ...
});

The adapter auto-detects the dialect from the Drizzle instance.

On this page