Skip to main content
Sprout uses Drizzle ORM with SQLite for data persistence. All schema definitions live in TypeScript, and migrations are automatically generated.

Overview

Drizzle provides a type-safe way to define database schemas and generates SQL migration files automatically. Key Files:
  • src/db/schema.ts - Schema definitions (source of truth)
  • drizzle.config.ts - Drizzle configuration
  • src/db/migrate.ts - Migration runner
  • drizzle/ - Generated migration SQL files
  • sprout.db - SQLite database file (created after first migration)

Initial Setup

After installing dependencies, initialize the database:
1

Ensure Configuration

Verify drizzle.config.ts exists:
drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "sqlite",
  dbCredentials: {
    url: "./sprout.db",
  },
});
2

Run Migrations

Apply the database schema:
cd sprout-backend
npm run db:migrate
Expected output:
Running migrations...
Migrations complete.
This creates sprout.db in the backend directory.
3

Verify Database

Check that the database was created:
ls -lh sprout.db
# Should show the database file
The default user is automatically seeded when you start the backend with npm run dev.

Database Schema

The schema is defined in src/db/schema.ts using Drizzle’s SQLite dialect.

Core Tables

Single-user application with a default user auto-seeded on startup.
export const users = sqliteTable("users", {
  id: text("id").primaryKey(),
  email: text("email").notNull().unique(),
  title: text("title"),
  desc: text("desc"),
  updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
  createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
});

Additional Tables

TablePurpose
topic_documentsS3-stored documents uploaded for topics
node_contentsExplanations, visualizations, and learning cards
assessmentsDiagnostic, quiz, and recall assessments
questionsMCQ and open-ended questions
answersStudent answers with scores and feedback
user_node_progressMastery scores and completion tracking
chat_sessionsTutoring chat sessions
chat_messagesChat history with roles and kinds
hint_eventsHint requests and responses
See src/db/schema.ts:1 for the complete schema with indexes and constraints.

Migration Commands

Drizzle provides three main commands for managing migrations:
Apply Migrations
npm run db:migrate
Runs all pending migrations from the drizzle/ folder.When to use:
  • First time setup
  • After pulling schema changes from Git
  • After generating new migrations
Implementation (src/db/migrate.ts):
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { db } from "./index";
import path from "path";

const migrationsFolder = path.resolve(__dirname, "../../drizzle");

console.log("Running migrations...");
migrate(db, { migrationsFolder });
console.log("Migrations complete.");

Migration Workflow

Recommended workflow for schema changes:
1

Modify Schema

Edit src/db/schema.ts with your changes:
// Add a new column
export const nodes = sqliteTable("nodes", {
  // ... existing columns
  difficulty: integer("difficulty").default(1),
});
2

Generate Migration

Create a migration file:
npm run db:generate
Review the generated SQL in drizzle/XXXX_*.sql:
ALTER TABLE nodes ADD COLUMN difficulty INTEGER DEFAULT 1;
3

Apply Migration

Run the migration:
npm run db:migrate
4

Commit Changes

Commit both the schema and migration files:
git add src/db/schema.ts drizzle/
git commit -m "Add difficulty column to nodes table"

Existing Migrations

Sprout ships with four migrations:
drizzle/
├── 0000_milky_wilson_fisk.sql          # Initial schema
├── 0001_nosy_gunslinger.sql            # Schema updates
├── 0002_flippant_colonel_america.sql   # More updates
├── 0003_little_rawhide_kid.sql         # Latest updates
└── meta/
    ├── 0000_snapshot.json
    ├── 0001_snapshot.json
    ├── 0002_snapshot.json
    ├── 0003_snapshot.json
    └── _journal.json
Drizzle auto-generates migration names. The meta folder contains schema snapshots for diffing.

Database Location

The database file location is configured via environment variable:
.env
DB_PATH=./sprout.db        # Default: in backend root
DB_PATH=/data/sprout.db    # Custom location
Configuration (drizzle.config.ts):
dbCredentials: {
  url: "./sprout.db",  // Hardcoded, but can read from env
}

Inspecting the Database

Use SQLite tools to inspect the database:
sqlite3 sprout.db

# List tables
.tables

# Describe schema
.schema nodes

# Query data
SELECT * FROM users;

# Exit
.quit

Common Operations

Delete the database file and re-run migrations:
cd sprout-backend
rm sprout.db
npm run db:migrate
This deletes all data. Use only in development.
Drizzle doesn’t support automatic rollbacks. Manually revert:
  1. Delete the migration file from drizzle/
  2. Edit drizzle/meta/_journal.json to remove the entry
  3. Run the SQL DOWN migration manually (you need to write it)
Or reset the database (see above).
The default user is auto-seeded on backend startup (src/index.ts:17):
const DEFAULT_USER_ID = "00000000-0000-0000-0000-000000000000";

async function ensureDefaultUser() {
  const existing = await db.select().from(users)
    .where(eq(users.id, DEFAULT_USER_ID));
  if (!existing.length) {
    await db.insert(users).values({
      id: DEFAULT_USER_ID,
      email: "[email protected]",
      title: "Default Learner",
    });
  }
}
For additional test data, add a seed script or use Drizzle Studio.
SQLite databases are single files - just copy:
cp sprout.db sprout.db.backup

# Or with timestamp
cp sprout.db "sprout.db.$(date +%Y%m%d_%H%M%S)"

Troubleshooting

The database schema is out of sync with migrations.Solution:
  1. Backup your data
  2. Reset the database: rm sprout.db
  3. Re-run migrations: npm run db:migrate
Error: Cannot find module './src/db/schema.ts'Solution: Check drizzle.config.ts schema path matches your directory structure.
Error: SQLITE_BUSY: database is lockedSolution:
  1. Close any SQLite browser tools
  2. Ensure only one backend instance is running
  3. Check for abandoned connections
Drizzle generated unexpected SQL.Solution:
  1. Review the generated SQL in drizzle/XXXX_*.sql
  2. If incorrect, delete the migration and fix your schema
  3. Re-generate with npm run db:generate
  4. Manually edit the SQL if needed (not recommended)

Next Steps

Schema Reference

Learn more about Drizzle schema syntax

Document Uploads

Configure S3 for document storage

Build docs developers (and LLMs) love