Setting up Drizzle
Database setup using Drizzle ORM with Bun's native SQLite driver, wired into the project's env
validation and src/lib conventions.
Prerequisites
- Bun project (uses
drizzle-orm/bun-sqlite, the Bun-native driver) src/lib/env.server.tsenv validation convention already in place
Drizzle 1.0 is currently at release candidate
rc5— installdrizzle-orm@rc5anddrizzle-kit@rc5exactly as shown below. Do not runbun add drizzle-ormwithout@rc5.
Steps
Install
bun add drizzle-orm@rc5 bun add -d drizzle-kit@rc5drizzle-kitis the CLI for schema pushes, migrations and Drizzle Studio — dev dependency only.Add scripts to
package.json- The
bun --bunprefix is required so drizzle-kit resolves the SQLite driver through Bun's runtime rather than a Node.js shim
{ "scripts": { "db:push": "bun --bun drizzle-kit push", "db:studio": "bun --bun drizzle-kit studio", }, }- The
Configure environment
- Add
DATABASE_URLto the server environment schema insrc/lib/env.server.ts
// src/lib/env.server.ts import { z } from "zod"; const serverEnvSchema = z.object({ DATABASE_URL: z.string().min(1, "DATABASE_URL is required"), }); export default serverEnvSchema.parse(process.env);- The database path is non-secret config, so it belongs in
.env(committed) rather than.env.local
# .env DATABASE_URL=file:./local.db- Add the database file to
.gitignore
local.db- Add
Create
drizzle.config.tsat the project root- Reads
process.envdirectly rather than@/lib/env.serveras drizzle-kit runs outside the app
// drizzle.config.ts import { defineConfig } from "drizzle-kit"; export default defineConfig({ out: "./drizzle", schema: "./src/lib/db.schema.ts", dialect: "sqlite", dbCredentials: { url: process.env.DATABASE_URL!, }, });- Reads
Define the schema
// src/lib/db.schema.ts import { int, sqliteTable, text } from "drizzle-orm/sqlite-core"; export const notesTable = sqliteTable("notes", { id: int().primaryKey({ autoIncrement: true }), content: text().notNull(), createdAt: int({ mode: "timestamp_ms" }).notNull(), updatedAt: int({ mode: "timestamp_ms" }).notNull(), });int({ mode: "timestamp_ms" })stores timestamps as milliseconds since epoch and maps them to JSDateobjects — assignnew Date()orDate.now()directly
Wire into project
- Create the client in
src/lib/db.server.ts, usingdrizzle-orm/bun-sqlite(the Bun-native driver)
// src/lib/db.server.ts import { drizzle } from "drizzle-orm/bun-sqlite"; import * as schema from "@/lib/db.schema"; import env from "@/lib/env.server"; export const db = drizzle(env.DATABASE_URL, { schema });// src/lib/notes.server.ts import { db } from "@/lib/db.server"; import { notesTable } from "@/lib/db.schema"; export async function listNotes() { return await db.select().from(notesTable); }// src/lib/notes.functions.ts import { createServerFn } from "@tanstack/react-start"; import { listNotes } from "@/lib/notes.server"; export const listNotesFn = createServerFn().handler(() => listNotes());- Create the client in
Push the schema
bun db:push
Verification
-
local.dbwas created after runningbun db:push -
bun db:studioopens and shows thenotestable
Environment variables
DATABASE_URL— SQLite file path, e.g.file:./local.db. Non-secret, goes in committed.env
Gotchas
- Must install
drizzle-orm@rc5/drizzle-kit@rc5— plainbun add drizzle-ormpulls a version that doesn't match this setup