Migrating from PostgreSQL to MySQL
Learn how to migrate from PostgreSQL to MySQL in your Next.js Drizzle SaaS application.
Important: This kit uses PostgreSQL by default. This guide describes a one-way migration for users who want to switch to MySQL. Tests continue using PGlite (PostgreSQL in-memory) regardless of your production database.
Why MySQL?
- Widespread hosting: Supported by most hosting providers, from shared hosting to cloud platforms
- PlanetScale: Serverless MySQL with branching, zero-downtime schema changes
- AWS Aurora MySQL: High availability MySQL-compatible database
- Existing infrastructure: Many organizations already run MySQL
Architecture
The database package uses an adapters pattern:
packages/database/src/ client.ts # Re-exports from active adapter adapters/ postgres.ts # PostgreSQL adapter (default) mysql.ts # MySQL adapter (you'll create this) test-utils/ pglite-db.ts # Tests always use PGlite (unchanged)You'll create a MySQL adapter and switch the client to use it.
Quick Start
1. Install mysql2 driver
pnpm --filter @kit/database add mysql22. Create MySQL adapter
Create packages/database/src/adapters/mysql.ts:
import { MySql2Database, drizzle } from 'drizzle-orm/mysql2';import mysql from 'mysql2/promise';import { databaseUrl } from '../database-url';import * as schema from '../schema/schema';declare global { var mysqlDb: MySql2Database<DatabaseSchema> | undefined;}export type DatabaseSchema = typeof schema;export type Database = MySql2Database<DatabaseSchema>;let db: Database;if (process.env.NODE_ENV === 'production') { db = createDrizzle();} else { if (!global.mysqlDb) { global.mysqlDb = createDrizzle(); } db = global.mysqlDb;}export { db };function createDrizzle() { const pool = mysql.createPool({ uri: databaseUrl, waitForConnections: true, connectionLimit: 10, queueLimit: 0, }); return drizzle(pool, { schema, mode: 'default' });}3. Update Better Auth provider
Edit packages/better-auth/src/auth.ts:
const database = drizzleAdapter(db, { provider: 'mysql', // Change from 'pg' usePlural: true,});4. Generate MySQL schema
This regenerates packages/database/src/schema/core.ts with MySQL types:
pnpm --filter @kit/better-auth schema:generate5. Switch to MySQL adapter
Edit packages/database/src/client.ts:
// Beforeexport { db, type DatabaseSchema } from './adapters/postgres';// Afterexport { db, type DatabaseSchema } from './adapters/mysql';6. Update Drizzle config
Edit packages/database/drizzle.config.mjs:
import { defineConfig } from 'drizzle-kit';const dialect = 'mysql'; // Change from 'postgresql'export default defineConfig({ schema: '../../packages/database/src/schema/schema.ts', out: '../../packages/database/src/schema', dialect, dbCredentials: { url: process.env.DATABASE_URL ?? 'mysql://root:password@127.0.0.1:3306/saas_kit', }, verbose: true, strict: true,});7. Update database URL validation
Edit packages/database/src/database-url.ts:
import * as z from 'zod';export const databaseUrl = z .url({ error: 'Please provide the variable DATABASE_URL as a valid URL pointing to a MySQL database', }) .default( process.env.NODE_ENV === 'production' ? process.env.DATABASE_URL! : 'mysql://root:password@127.0.0.1:3306/saas_kit', ) .parse(process.env.DATABASE_URL);8. Update docker-compose.dev.yml
Replace the PostgreSQL service with MySQL:
services: mysql: image: mysql:8.0 container_name: next-drizzle-saas-kit-mysql environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: saas_kit ports: - '3306:3306' volumes: - mysql_data:/var/lib/mysql healthcheck: test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost'] interval: 10s timeout: 5s retries: 5volumes: mysql_data:9. Regenerate migrations
rm -rf packages/database/src/schema/metarm packages/database/src/schema/*.sqlpnpm --filter "@kit/database" drizzle:generate10. Start MySQL and push migrations
Start the MySQL container:
pnpm compose:dev:upPush the migrations to the database:
pnpm --filter "@kit/database" drizzle:migrate11. Seed the database (optional)
If you have seed scripts configured:
pnpm seedEnvironment Variables
Local MySQL
DATABASE_URL=mysql://root:password@127.0.0.1:3306/saas_kitAWS RDS MySQL
DATABASE_URL=mysql://admin:password@your-instance.region.rds.amazonaws.com:3306/saas_kitCleanup
Remove unused PostgreSQL packages:
pnpm --filter @kit/database remove postgresKeep @electric-sql/pglite - it's used by tests.