VibORM
Relations

One-to-Many

Define one-to-many relationships connecting a single record to multiple related records

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.oneToMany(() => post),  // Array of posts
});

// Each post belongs to one user (many-to-one side)
const post = s.model({
  id: s.string().id().ulid(),
  title: s.string(),
  authorId: s.string(),
  author: s.manyToOne(() => user)
    .fields("authorId")
    .references("id"),
});

Characteristics

AspectValue
ReturnsArray (Post[])
FK locationOn the "many" side
Can be emptyYes (empty array)
Optional modifierNo (arrays are never null)

No FK on This Side

The one-to-many side does NOT have a foreign key. The FK is on the many-to-one side:

// ✅ Correct - no FK configuration needed
posts: s.oneToMany(() => post)

// ❌ Wrong - oneToMany doesn't have fields/references
posts: s.oneToMany(() => post).fields("???")

Minimal Configuration

One-to-many relations have minimal configuration since they don't own the FK:

s.oneToMany(() => post)
// That's it! No .fields(), .references(), or .optional()

Available Methods

MethodDescription
.name(relationName)Set a custom relation name

That's it! All FK configuration is done on the manyToOne side.

Complete Example

const user = s.model({
  id: s.string().id().ulid(),
  email: s.string().unique(),
  name: s.string(),
  // One user has many posts
  posts: s.oneToMany(() => post),
  // One user has many comments
  comments: s.oneToMany(() => comment),
});

const post = s.model({
  id: s.string().id().ulid(),
  title: s.string(),
  content: s.string(),
  authorId: s.string(),
  // Many posts belong to one user
  author: s.manyToOne(() => user)
    .fields("authorId")
    .references("id"),
  // One post has many comments
  comments: s.oneToMany(() => comment),
});

const comment = s.model({
  id: s.string().id().ulid(),
  content: s.string(),
  authorId: s.string(),
  postId: s.string(),
  // Many comments belong to one user
  author: s.manyToOne(() => user)
    .fields("authorId")
    .references("id"),
  // Many comments belong to one post
  post: s.manyToOne(() => post)
    .fields("postId")
    .references("id"),
});

Querying One-to-Many

// Include posts when fetching user
const user = await client.user.findUnique({
  where: { id: "user_123" },
  include: { posts: true },
});
// user.posts: Post[]

// Filter and paginate included posts
const user = await client.user.findUnique({
  where: { id: "user_123" },
  include: {
    posts: {
      where: { published: true },
      orderBy: { createdAt: "desc" },
      take: 10,
    }
  }
});

// Filter users by their posts
const authors = await client.user.findMany({
  where: {
    posts: {
      some: { published: true }  // Has at least one published post
    }
  }
});

// Users with ALL published posts
const consistentAuthors = await client.user.findMany({
  where: {
    posts: {
      every: { published: true }
    }
  }
});

// Users with NO posts
const newUsers = await client.user.findMany({
  where: {
    posts: {
      none: {}
    }
  }
});
// Create user with posts
const user = await client.user.create({
  data: {
    email: "alice@example.com",
    name: "Alice",
    posts: {
      create: [
        { title: "First Post", content: "Hello!" },
        { title: "Second Post", content: "World!" },
      ]
    }
  },
  include: { posts: true },
});

// Add posts to existing user
await client.user.update({
  where: { id: "user_123" },
  data: {
    posts: {
      create: { title: "New Post", content: "Content" },
    }
  }
});

Common Patterns

Blog with Categories

const category = s.model({
  id: s.string().id().ulid(),
  name: s.string().unique(),
  posts: s.oneToMany(() => post),
});

const post = s.model({
  id: s.string().id().ulid(),
  title: s.string(),
  categoryId: s.string(),
  category: s.manyToOne(() => category)
    .fields("categoryId")
    .references("id"),
});

Organization with Members

const organization = s.model({
  id: s.string().id().ulid(),
  name: s.string(),
  members: s.oneToMany(() => membership),
});

const membership = s.model({
  id: s.string().id().ulid(),
  role: s.enum(["ADMIN", "MEMBER"]),
  orgId: s.string(),
  userId: s.string(),
  organization: s.manyToOne(() => organization)
    .fields("orgId")
    .references("id"),
  user: s.manyToOne(() => user)
    .fields("userId")
    .references("id"),
});

On this page