Relations
Relations describe how your models connect. Zanith uses them to build JOINs automatically — you never write JOIN SQL by hand.
Why relations matter
Without relations, getting data from two tables means writing manual SQL JOINs or making multiple queries and stitching results together. With relations, you write.with({ author: true }) and Zanith looks up the foreign key, builds the JOIN, and returns typed results — automatically.
Relations also enable multi-hop traversal (Contract → Organization → Owner), many-to-many through junction tables, and automatic nullable propagation for LEFT JOINs.
Visual overview
Entity relationship diagram — each arrow becomes an automatic JOIN
belongsTo
The most common relation. Use it when this modelhas a foreign key column that points to another model's primary key.
// Post has an authorId column → points to User.idrelations: { author: m.belongsTo(User, { field: 'authorId', // FK column on THIS model references: 'id', // PK column on the TARGET model onDelete: 'cascade', // optional: cascade, set_null, restrict }),}When you query with .with({ author: true }), Zanith generates:
LEFT JOIN "users" AS "author" ON "posts"."author_id" = "author"."id"hasMany
The reverse of belongsTo. Use it when another model has a foreign key pointing to this model. The FK lives on the other side.
// User doesn't have a FK — Post.authorId points to Userrelations: { posts: m.hasMany(() => Post, { foreignKey: 'authorId', // FK column on the OTHER model }),}hasOne
One-to-one relationship. Like hasMany but only one record on the other side. Typically used for profile/settings patterns.
relations: { profile: m.hasOne(() => Profile, { field: 'id', references: 'userId', }),}manyToMany
When two models relate through a junction table. Requires a separate model for the junction. Zanith generates two JOINs automatically.
// The junction modelconst PostTag = defineModel((m) => ({ name: 'PostTag', table: 'post_tags', fields: { ...m.id(), postId: m.uuid().index(), tagId: m.uuid().index(), }, relations: { post: m.belongsTo(Post, { field: 'postId', references: 'id' }), tag: m.belongsTo(Tag, { field: 'tagId', references: 'id' }), },})); // On the Post model:relations: { tags: m.manyToMany(Tag, { through: PostTag, // the junction model fromField: 'postId', // junction FK → this model toField: 'tagId', // junction FK → target model }),}When you query .with({ tags: true }), Zanith generates:
LEFT JOIN "post_tags" AS "tags_junction" ON "posts"."id" = "tags_junction"."postId"LEFT JOIN "tags" AS "tags" ON "tags_junction"."tagId" = "tags"."id"Relation types summary
| Type | FK lives on | When to use | Example |
|---|---|---|---|
belongsTo | This model | This model has the foreign key | Post → User |
hasMany | Other model | Other model has FK pointing here | User → Posts |
hasOne | Other model | One-to-one (other side is unique) | User → Profile |
manyToMany | Junction table | Many-to-many through junction | Post ↔ Tag |
Next steps
- Relational Queries — how to use .with() to JOIN related models in queries
- Enums — define fixed value sets for fields like status and role