What is nORM?
nORM (no ORM) is a SQL-first code generator for developers that want typed data access without moving query design into an ORM.
You write schema and repository SQL. nORM generates typed models and repository methods from those files.
If you like sqlc, nORM should feel familiar. It keeps the same SQL-first workflow while adding helpers for dynamic filters, dynamic sorting, model embedding, and patch-style updates.
Who this is for
nORM is a good fit if you want to:
- keep SQL and query plans visible
- remove repetitive data-mapping code
- expose strongly typed application APIs
- support runtime filtering and sorting without handwritten SQL string assembly
If you prefer building queries through ORM method chains, nORM is probably not the right tool.
In a nutshell
1. Define your schema
sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
name text NOT NULL,
rating int
);
CREATE TABLE books (
id SERIAL PRIMARY KEY,
author_id integer NOT NULL REFERENCES authors(id),
book_type text NOT NULL DEFAULT 'FICTION',
title text NOT NULL
);2. Write repository SQL
sql
-- repo_name: AuthorRepo
-- name: get_all_authors :many
SELECT * FROM authors
WHERE rating = :rating;3. Generate code
sh
norm generatepython
from psycopg import AsyncConnection as Connection
from pydantic import BaseModel
from .models import Author
class AuthorRepo:
def __init__(self, db: Connection) -> None:
self.db = db
async def get_all_authors(
self,
rating: int,
) -> list[Author]:
query = """
SELECT
authors.id AS id,
authors.name AS name,
authors.rating AS rating
FROM authors
WHERE
authors.rating = %(rating)s
"""
params = {
"rating": rating,
}
async with self.db.cursor() as cur:
await cur.execute(query, params)
result = [
Author(
id=item[0],
name=item[1],
rating=item[2],
)
async for item in cur
]
return resultpython
from pydantic import BaseModel
class Author(BaseModel):
id: int
name: str
rating: int | Nonego
// coming soonrust
// coming soontypescript
// coming soon4. Use the generated API
python
from norm_out.author_repo import AuthorRepo
async def main():
async with get_db() as db:
repo = AuthorRepo(db=db)
many = await repo.get_all_authors(rating=5)go
// coming soonrust
// coming soontypescript
// coming soonWhat nORM adds
Beyond basic SQL-to-code generation, nORM includes features that are awkward to maintain by hand:
- dynamic filtering and sorting macros with validation
- partial updates through
:_fieldpatch semantics - model embedding for joins, including nullable embeds through
n.nembed()forLEFT JOIN - one named-parameter style across supported dialects