No Migration Sprawl
Fresh databases don't replay 3 years of history. Run your SQL files directly and get the current schema. Test databases rebuild in seconds.
Express what ORMs can't. Compound keys, check constraints, triggers, stored procedures—real SQL.

noorm is NOT an ORM. ORMs replace your SQL with inefficient abstractions. noorm runs your SQL directly.
Raw SQL lets you design databases properly. Inherited keys, base/subtype hierarchies, complex constraints—the relational model as it was intended.
noorm makes it manageable. SQL files define your schema. Changes evolve existing databases. Execution tracking, environment configs, encrypted secrets, team collaboration—all handled from your terminal. You focus on the SQL.
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
npm install -g @noormdev/cli
# Launch the TUI
noormFrom the terminal interface, set up your project:
Or use headless mode for scripting:
noorm -H init
noorm -H identity set "Your Name"
noorm -H config add
noorm -H run buildCreate 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 -H 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 -H change ff # Fast-forward: apply pending changesNeed a fresh test database? Create another config and build—no changes needed:
noorm -H config add # Create test config
noorm -H run build # Fresh DB gets current schema directlySQL files = current schema. Changes = how to get existing databases there.