zanith

Installation

Get Zanith running in your project in under a minute.

Install

Install the core package and your preferred database driver. Postgres has two drivers; SQLite ships as a separate adapter.

SHELLterminal
npm install zanith
 
# Postgres — pick one:
npm install pg # node-postgres (most popular)
npm install postgres # postgres.js (modern alternative)
 
# Or SQLite:
npm install better-sqlite3

How Zanith works — the pipeline

Schema

.ts or .zn

Parser
Graph

in-memory

Query Builder
SQL Compiler
PostgreSQL

Traditional ORMs

  • ×Change schema → regenerate (minutes)
  • ×500k lines generated for 1000 models
  • ×TS compiler crashes on large schemas
  • ×Full rebuild on every change

Zanith

  • Change schema → restart (59ms)
  • Zero lines generated
  • No generated files to compile
  • Incremental graph patch (0.7ms)

Create your first schema

  1. 01

    Define your models

    Each model maps to a database table. Fields map to columns. Relations map to foreign keys.

  2. 02

    Create the client

    Pass your models and a database adapter to createZanith(). No generation step.

  3. 03

    Query with full types

    findMany(), create(), update(), delete() — all return typed results. Wrong field names are compile errors.

TSschema/user.ts
import { defineModel, defineEnum } from 'zanith';
 
export const Role = defineEnum({
name: 'Role',
values: ['ADMIN', 'USER', 'MODERATOR'],
});
 
export const User = defineModel((m) => ({
name: 'User',
table: 'users',
fields: {
...m.id(), // uuid primary key
...m.timestamps(), // createdAt + updatedAt
email: m.string().unique(),
name: m.string().nullable(),
role: m.enum(Role).default('USER'),
},
relations: {
posts: m.hasMany(() => Post, { foreignKey: 'authorId' }),
},
}));
 
export const Post = defineModel((m) => ({
name: 'Post',
table: 'posts',
fields: {
...m.id(),
...m.timestamps(),
title: m.string(),
content: m.text().nullable(),
published: m.boolean().default(false),
authorId: m.uuid(),
},
relations: {
author: m.belongsTo(User, { field: 'authorId', references: 'id' }),
},
}));

Connect and query

Create a Zanith client, pass your models and a database adapter, and start querying.

TSapp.ts
import { createZanith } from 'zanith';
import { PgAdapter } from 'zanith/adapters/pg';
import { User, Post, Role } from './schema/user';
 
const db = await createZanith({
models: { User, Post },
enums: [Role],
adapter: new PgAdapter({
connectionString: 'postgresql://user:pass@localhost:5432/mydb',
}),
});
 
// Fully typed — email: string, name: string | null
const users = await db.user.findMany({
where: { email: { contains: '@example.com' } },
orderBy: { createdAt: 'desc' },
take: 10,
});
 
// Relational query — auto JOIN
const posts = await db.post.query()
.with({ author: true })
.select(({ post, author }) => ({
title: post.title,
authorEmail: author.email,
}))
.where(({ post }) => post.published.eq(true))
.limit(20)
.execute();
 
await db.disconnect();

Scalar types

Every field builder method maps to a PostgreSQL type and a TypeScript type.

BuilderPostgreSQLTypeScript
m.string()TEXTstring
m.text()TEXTstring
m.uuid()UUIDstring
m.int()INTEGERnumber
m.float()DOUBLE PRECISIONnumber
m.boolean()BOOLEANboolean
m.datetime()TIMESTAMPDate
m.bigint()BIGINTbigint
m.json()JSONBunknown
m.decimal()NUMERICnumber
m.bytes()BYTEABuffer

Field modifiers

ModifierEffectExample
nullable()Field can be NULLm.string().nullable()
unique()UNIQUE constraintm.string().unique()
default(value)Default valuem.boolean().default(false)
index()Database indexm.uuid().index()
comment(text)Documentationm.string().comment('Login email')
map(col)Custom column namem.string().map('first_name')
array()PostgreSQL arraym.string().array()
autoUpdate()Auto-update on changem.datetime().autoUpdate()

Built-in mixins

Mixins are reusable field groups. Spread them into your model fields.

MixinProvidesFields
m.id()UUID primary keyid: uuid, pk, default(uuid())
m.intId()Auto-increment PKid: int, pk, default(autoincrement())
m.timestamps()Created + updatedcreatedAt: datetime, updatedAt: datetime autoUpdate
m.auditable()Audit trailcreatedById: uuid?, updatedById: uuid?
m.orgScoped()Multi-tenant FKorganizationId: uuid, indexed
m.softDelete()Soft deletedeletedAt: datetime?

Try Studio

Once you have a running Zanith client, the same binary launches a full web UI against your connection — no separate install.

SHELLterminal
npx zanith studio --allow-sql
# studio listening on http://127.0.0.1:4321

Next steps

Now that you have a basic setup, explore: