Your First Query
Go from zero to querying your database in 60 seconds. No generation step. No waiting.
What you'll build
By the end of this page, you'll have a working Zanith setup that defines a User model, connects to PostgreSQL, and runs a fully typed query.
- 01
Define your schema
Create a model that describes your database table — its fields, types, and constraints.
- 02
Create the client
Pass your models and a database connection to createZanith(). The schema compiles into a runtime graph in ~59ms.
- 03
Query with full types
Call findMany(), create(), or any other method. TypeScript knows the exact shape of your results.
Step 1 — Define a model
A model describes a database table. Each field maps to a column. The TypeScript builder carries type information through the entire chain, so your queries are always type-safe — without generating any code.
import { defineModel } from 'zanith'; export const User = defineModel((m) => ({ name: 'User', table: 'users', fields: { ...m.id(), // uuid primary key with auto-generated default ...m.timestamps(), // createdAt + updatedAt email: m.string().unique(), name: m.string().nullable(), }, relations: {},}));Step 2 — Create the client
Pass your models and a database adapter to createZanith(). That's it. No prisma generate. No build step. The schema compiles into a runtime graph in milliseconds — even with 1000 models.
import { createZanith } from 'zanith';import { PgAdapter } from 'zanith/adapters/pg';import { User } from './schema'; const db = await createZanith({ models: { User }, adapter: new PgAdapter({ connectionString: process.env.DATABASE_URL, }),});Step 3 — Query
Now query your data. Every method is fully typed — TypeScript knows thatusers is an array with id, email,name, createdAt, and updatedAt fields.
// Find users — fully typed resultconst users = await db.user.findMany({ where: { email: { contains: '@example.com' } }, orderBy: { createdAt: 'desc' }, take: 10,});// users: Array<{ id: string, email: string, name: string | null, ... }> // Create a new recordconst newUser = await db.user.create({ data: { email: '[email protected]', name: 'Alice' },}); // Wrong field names are compile errors:// db.user.findMany({ where: { emale: 'test' } });// ^^^^^ TS Error! await db.disconnect();How the pipeline works
Under the hood, every query passes through a structured pipeline. Your code never touches SQL directly — the engine builds parameterized queries from your schema graph.
schema
graph
typed API
builds AST
parameterized
executes
The entire pipeline — schema lookup, AST construction, SQL compilation — takes about 4 microseconds per query. The bottleneck is always the database round trip, never the engine.
Next steps
Now that you have a basic setup, explore:
- TypeScript Setup — how type inference works without code generation
- Relations — connect models with belongsTo, hasMany, manyToMany
- Relational queries — auto JOINs, multi-hop traversal
- Mixins — reusable field groups (timestamps, audit, soft delete)