No Migration Sprawl
SQL files show what IS, not archaeology of what WAS. Fresh databases build in seconds from current schema—no replaying 3 years of history.
Manage SQL files, track changes, and run builds across dev, staging, and production—no ORM required.

noorm is a command-line tool for managing SQL-first database applications. It handles everything ORMs won't touch: compound keys, check constraints, triggers, stored procedures—the full relational model.
What it does:
You write SQL. noorm executes it, tracks what ran, and keeps multiple environments in sync.
ORMs push you toward a pattern: every table gets a surrogate ID, relationships happen through foreign keys, and you join your way back to find what you need. It works—until you're seven joins deep trying to figure out which user owns a deeply nested entity, and your messy left joins are adding NULL rows or creating cartesian products.
Proper relational design uses inherited keys. Instead of giving every entity an independent identity, child entities inherit their parent's key as part of their own.
Example: A todo list
users
→ user_id (surrogate, this is the root)
todos
→ user_id + created_at (inherits from user, no separate todo_id)
todo_items
→ user_id + created_at + item_index (inherits from todo)With inherited keys, a todo_item carries its lineage in its identity. You don't need joins to find the user—it's right there in the key. The deeper your schema goes, the more this matters.
Try working that into your ORM. I'll wait...
ORMs love polymorphic associations: a comments table with commentable_type and commentable_id. Fast, flexible—and completely breaks referential integrity. Complex app logic, no foreign keys, slow and awkward statistics, and even more awkward queries.
Proper relational design solved this years ago with basetype-subtypes:
independent entities: user, group
dependent entities: profile
basetype-subtypes: post → user_post, group_post
photo → user_photo, group_photo, profile_photo, user_post_photo, ...
comment → user_comment, group_comment, post_comment, comment_comment, ...
tag → post_tag, photo_tag, comment_tag, ...Each relationship gets its own table with proper constraints against its parent. A user_post has a foreign key to user and post. A group_photo has a foreign key to group and photo. No nulls, no type columns, no ambiguity.
You work with existence and non-existence—not "maybe exists" or calculate. You depend on physical existence, not hopeful logic. Statistics are straightforward. Queries are clean. The database enforces integrity at every level. Illegal states become impossible. The trade-off is more tables, but the benefit is less app logic.
You pay for bad relational design later in complexity and bugs.
# Install (no sudo needed)
curl -fsSL https://noorm.dev/install.sh | sh
# Or via npm
npm install -g @noormdev/cli
# Launch the interactive TUI
noorm uiCorporate network? If
noorm.devis blocked, use the GitHub mirror:bashcurl -fsSL https://raw.githubusercontent.com/noormdev/noorm/master/install.sh | sh
From the interactive TUI (noorm ui), set up your project:
Or drive the same flow from the non-interactive CLI — every command runs headlessly by default and emits structured output you can pipe into scripts:
noorm run build # Build the schema from SQL files
noorm change ff # Apply all pending changes
noorm --json db explore # Inspect the database as JSON
noorm vault set API_KEY ... # Push a team secret to the encrypted vaultWizard-only operations (config add, config edit, secret management) launch the TUI when invoked from the CLI — set them up once via noorm ui, then drive everything else from scripts.
Create your SQL files:
mkdir -p sql/01_tables
echo "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);" > sql/01_tables/001_users.sqlBuild your schema:
noorm run build✓ Executed 1 fileNow your schema needs to evolve. Update your SQL file AND create a change:
# Update sql/01_tables/001_users.sql (add email column)
# Create changes/2024-01-add-email/forward.sql
noorm change ff # Fast-forward: apply pending changesNeed a fresh test database? Add another config and build—no changes needed:
noorm ui # Use the wizard to add a `test` config
noorm config use test
noorm run build # Fresh DB gets current schema directlySQL files = current schema. Changes = how to get existing databases there.