Skip to content

Drizzle

gau supports Drizzle ORM 1.0, starting with 1.0.0-rc.4, for SQLite and PostgreSQL. Drizzle 0.x is no longer supported. This guide uses Turso for SQLite; Cloudflare D1 uses the same schema with a different client.

Email login also needs a verification table, passed as the fourth argument to DrizzleAdapter.


  1. Terminal window
    npm i drizzle-orm@1.0.0-rc.4 @libsql/client
    npm i -D drizzle-kit@1.0.0-rc.4
  2. Define the user and account fields shown below. sessionState and the account’s createdAt are optional. You can add application columns and an optional text role column to the user table. Table names are up to you; keep the TypeScript field names shown here.

    snakeCase.table() maps fields such as emailVerified to SQL columns such as email_verified. User IDs and account identifiers must be non-null strings. User timestamps must use columns that accept JavaScript Date values.

    schema.ts
    import { integer, primaryKey, snakeCase, text } from 'drizzle-orm/sqlite-core'
    export const Users = snakeCase.table('users', {
    id: text().primaryKey().$defaultFn(() => crypto.randomUUID()),
    name: text(),
    email: text().unique(),
    emailVerified: integer({ mode: 'boolean' }),
    image: text(),
    createdAt: integer({ mode: 'timestamp' }).$defaultFn(() => new Date()),
    updatedAt: integer({ mode: 'timestamp' }).$defaultFn(() => new Date()),
    })
    export type SelectUser = typeof Users.$inferSelect
    export type InsertUser = typeof Users.$inferInsert
    export const Accounts = snakeCase.table(
    'accounts',
    {
    userId: text()
    .notNull()
    .references(() => Users.id, { onDelete: 'cascade' }),
    type: text().notNull(),
    provider: text().notNull(),
    providerAccountId: text().notNull(),
    refreshToken: text(),
    accessToken: text(),
    expiresAt: integer(),
    tokenType: text(),
    scope: text(),
    idToken: text(),
    sessionState: text(),
    createdAt: integer({ mode: 'timestamp' }).$defaultFn(() => new Date()),
    },
    account => [
    primaryKey({
    columns: [account.provider, account.providerAccountId],
    }),
    ],
    )
    export type SelectAccount = typeof Accounts.$inferSelect
    export type InsertAccount = typeof Accounts.$inferInsert
  3. Configure the schema path and database credentials for migrations.

    drizzle.config.ts
    import { defineConfig } from 'drizzle-kit'
    export default defineConfig({
    schema: './schema.ts',
    dialect: 'turso',
    dbCredentials: {
    authToken: process.env.TURSO_AUTH_TOKEN!,
    url: process.env.TURSO_DB_URL!,
    },
    })

    Now you can run npx drizzle-kit push to apply your schema to the database.

  4. Initialize the Drizzle client with your database connection.

    db.ts
    import { createClient } from '@libsql/client'
    import { drizzle } from 'drizzle-orm/libsql'
    const client = createClient({
    url: process.env.TURSO_DB_URL!,
    authToken: process.env.TURSO_AUTH_TOKEN!,
    })
    export const db = drizzle({ client })

    For Cloudflare D1, create the client from your Worker binding instead:

    db.ts
    import { env } from 'cloudflare:workers'
    import { drizzle } from 'drizzle-orm/d1'
    export const db = drizzle(env.DB)
  5. Finally, pass the Drizzle client and schema tables to the DrizzleAdapter in your createAuth configuration.

    auth.ts
    import { DrizzleAdapter } from '@rttnd/gau/adapters/drizzle'
    import { createAuth } from '@rttnd/gau/core'
    import { GitHub } from '@rttnd/gau/oauth'
    import { db } from './db'
    import { Accounts, Users } from './schema'
    export const auth = createAuth({
    adapter: DrizzleAdapter(db, Users, Accounts),
    providers: [
    GitHub({
    clientId: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    ],
    jwt: {
    secret: process.env.AUTH_SECRET!,
    },
    })
    export type Auth = typeof auth

Upgrade both drizzle-orm and drizzle-kit to the versions above. The adapter call remains DrizzleAdapter(db, Users, Accounts).

  • Move casing: 'snake_case' from the database client and Kit configuration to snakeCase.table() in the schema. Preserve existing SQL column names; explicit column names still work.
  • Use drizzle({ client }) for libSQL and PostgreSQL clients. D1 continues to use drizzle(env.DB).
  • Remove the old schema client option. Gau does not require relational queries. If your application uses them, migrate to Drizzle’s defineRelations() API and pass relations to the client.
  • Use timestamp() in PostgreSQL and integer({ mode: 'timestamp' }) in SQLite for the user’s createdAt and updatedAt fields. Text timestamp columns do not meet the adapter’s schema contract.

Existing Drizzle Kit migration history also needs the Drizzle 1.0 upgrade procedure. Changing the adapter alone does not require new auth tables or renamed columns.