One-to-Many
Define one-to-many relationships connecting a single record to multiple related records
A one-to-many relationship is what an s.toMany slot paired with an s.toOne
slot spells, read from the collection side. The same pair read from the
singular side is many-to-one.
Basic Example
import { s } from "viborm";
// One user has many posts
const user = s.model({
id: s.string().id().ulid(),
email: s.string().unique(),
posts: s.toMany(() => post), // Array of posts
});
// Each post belongs to one user (the singular side owns the FK)
const post = s.model({
id: s.string().id().ulid(),
title: s.string(),
authorId: s.string(),
author: s.toOne(() => user)
.fields("authorId")
.references("id"),
});
Characteristics
| Aspect | Value |
|---|---|
| Returns | Array (Post[]) |
| FK location | On the “many” side |
| Can be empty | Yes (empty array) |
| Optional modifier | None exists — arrays are never null |
No FK on This Side
This side does NOT own a foreign key, so it needs no configuration. All FK setup lives on the many-to-one side:
// ✅ Correct - no FK configuration needed
posts: s.toMany(() => post)
// ❌ Does not compile - a collection slot has no fields/references
posts: s.toMany(() => post).fields("???")
A collection slot paired with a singular one exposes .name(). (The junction
methods — .through(), .source(), .target(), .onDelete(), .onUpdate() —
belong to a toMany whose partner is also a toMany; see
many-to-many.) See the relation method
reference.
The singular partner is not optional scaffolding: a toMany whose target model
declares nothing back is refused with R002, and if neither endpoint completes
.fields(...).references(...) the edge is refused with FK004.
Querying One-to-Many
// Include posts, filtered and paginated
const user = await client.user.findUnique({
where: { id: "user_123" },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: "desc" },
take: 10,
}
}
});
// user.posts: Post[]
// Filter users by their posts
const authors = await client.user.findMany({
where: {
posts: {
some: { published: true } // Has at least one published post
}
}
});
See To-Many Relation Filters for some/every/none and Nested Writes for creating related records.
Common Pattern: Blog with Categories
const category = s.model({
id: s.string().id().ulid(),
name: s.string().unique(),
posts: s.toMany(() => post),
});
const post = s.model({
id: s.string().id().ulid(),
title: s.string(),
categoryId: s.string(),
category: s.toOne(() => category)
.fields("categoryId")
.references("id"),
});