• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
nbbaier

nbbaier

drizzleMigrations

Type-safe Drizzle migrations for Val Town (no filesystem needed)
Public
Like
drizzleMigrations
Home
Code
7
example
4
.vtignore
AGENTS.md
README.md
biome.json
deno.json
main.ts
Branches
1
Pull requests
Remixes
History
Environment variables
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
…
Viewing readonly version of main branch: v26
View latest version
README.md

Type-Safe Drizzle Migrations for Val Town

A complete working example of filesystem-free migrations for Drizzle ORM.

What's Included

Five vals that work together:

  1. @nbbaier/drizzleMigrations - Core migration library
  2. @nbbaier/drizzleSchema - Example schema definitions
  3. @nbbaier/runDrizzleMigrations - HTTP endpoint to run migrations
  4. @nbbaier/inspectDrizzleSchema - View generated SQL
  5. @nbbaier/drizzleExampleApp - Working API example

Quick Start (5 minutes)

1. Run Migrations

Visit: https://nbbaier-runDrizzleMigrations.web.val.run

You'll see:

{ "success": true, "version": 1, "applied": ["users", "posts"], "skipped": [] }

This created the users and posts tables in your SQLite database.

2. Try the API

Visit: https://nbbaier-drizzleExampleApp.web.val.run/users

Create a user:

curl -X POST https://nbbaier-drizzleExampleApp.web.val.run/users \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}'

3. Inspect Generated SQL (Optional)

Visit: https://nbbaier-inspectDrizzleSchema.web.val.run

You'll see the actual CREATE TABLE statements that were generated.

How to Use in Your Project

Step 1: Fork or Reference the Migration Library

import { migrate, generateCreateTableSQL, } from "https://esm.town/v/nbbaier/drizzleMigrations";

Or fork @nbbaier/drizzleMigrations to your own account.

Step 2: Define Your Schema

Create a val with your tables:

import { sqliteTable, text, integer } from "npm:drizzle-orm/sqlite-core"; export const myTable = sqliteTable("my_table", { id: integer("id").primaryKey({ autoIncrement: true }), name: text("name").notNull(), });

Step 3: Create a Migration Runner

import { sqlite } from "https://esm.town/v/std/sqlite"; import { migrate } from "https://esm.town/v/nbbaier/drizzleMigrations"; import * as schema from "https://esm.town/v/YOUR_USERNAME/yourSchema"; export default async function (req: Request) { const result = await migrate(sqlite, schema); return Response.json(result); }

Step 4: Use Your Schema

import { drizzle } from "npm:drizzle-orm/better-sqlite3"; import { sqlite } from "https://esm.town/v/std/sqlite"; import * as schema from "https://esm.town/v/YOUR_USERNAME/yourSchema"; const db = drizzle(sqlite, { schema }); // Now you have full type safety! const results = await db.select().from(schema.myTable);

Features

  • ✅ Type-safe - Full TypeScript inference from your schema
  • ✅ No filesystem - Works in Val Town's sandboxed environment
  • ✅ Auto migrations - Creates new tables automatically
  • ✅ Change detection - Warns when schemas change
  • ✅ Foreign keys - Handles relationships correctly
  • ✅ Lightweight - ~300 lines, zero dependencies beyond drizzle-orm

API Reference

migrate(db, tables, options?)

Run migrations against your database.

Parameters:

  • db - Val Town SQLite instance
  • tables - Object with table definitions
  • options.dryRun - (optional) Preview without executing

Returns:

{ applied: string[], // New tables created skipped: string[], // Unchanged tables version: number // Migration version }

generateCreateTableSQL(table)

Generate CREATE TABLE SQL for a single table.

const sql = generateCreateTableSQL(myTable); console.log(sql); // CREATE TABLE IF NOT EXISTS "my_table" ( // "id" INTEGER PRIMARY KEY AUTOINCREMENT, // ... // );

generateSchemaSQL(tables)

Generate SQL for all tables and indexes.

const sql = generateSchemaSQL({ users, posts });

How It Works

  1. Reads Drizzle metadata - Uses Symbol keys to introspect schema
  2. Generates SQL - Walks column definitions to build CREATE TABLE
  3. Tracks state in SQLite - Stores migration versions in __drizzle_migrations table
  4. Detects changes - Compares SQL hashes to previous version
  5. Safe updates - Only creates new tables, warns about changes

Schema Changes

When you modify an existing table:

  1. Update your schema val
  2. Run migrations again
  3. You'll see: [CHANGED] Table schema changed: users
  4. Write manual ALTER TABLE or recreate the table

Example manual migration:

await sqlite.execute(` ALTER TABLE users ADD COLUMN bio TEXT; `);

Comparison to Drizzle Kit

FeatureDrizzle KitThis Solution
FilesystemRequiredNone
Val TownNoYes
Size~5MB~10KB
Auto migrationsYesNew tables only
Type safetyYesYes

Tips

Add Authentication

export default async function (req: Request) { const auth = req.headers.get("authorization"); if (auth !== `Bearer ${Deno.env.get("MIGRATION_TOKEN")}`) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } const result = await migrate(sqlite, schema); return Response.json(result); }

Set MIGRATION_TOKEN in Val Town secrets.

Dry Run

See what would happen without executing:

const result = await migrate(sqlite, schema, { dryRun: true });

Multiple Environments

Each Val Town user has their own SQLite database, so you can test changes before deploying.

Examples

Check out:

  • @nbbaier/drizzleSchema - Schema definitions
  • @nbbaier/runDrizzleMigrations - Migration runner
  • @nbbaier/drizzleExampleApp - Working API

License

MIT - Use freely!

Questions?

Open an issue or ask in Val Town Discord.

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.