Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

One-to-One

Define one-to-one relationships connecting a single record to exactly one other record

Basic Example

import { s } from "viborm";

// User has one profile
const user = s.model({
  id: s.string().id().ulid(),
  email: s.string().unique(),
  profile: s.oneToOne(() => profile).optional(),
});

// Profile belongs to one user
const profile = s.model({
  id: s.string().id().ulid(),
  bio: s.string(),
  userId: s.string().unique(),  // FK field, unique for 1:1
  user: s.oneToOne(() => user)
    .fields("userId")
    .references("id"),
});

Which Side Owns the FK?

The distinctive rule of one-to-one: exactly one side has the foreign key, and the FK field must be .unique() — that’s what makes the relation 1 instead of many-to-one.

Side Has FK Configuration
Owner (Profile) Yes .fields("userId").references("id")
Non-owner (User) No .optional() if nullable, or nothing

Configuration

One-to-one relations use the shared to-one methods — see the relation method reference for details and referential actions:

s.oneToOne(() => user)
  .fields("userId")     // FK field(s) on this model (owning side only)
  .references("id")     // Referenced field(s) on the target
  .optional()           // Relation can be null
  .onDelete("cascade")  // Referential action on delete
  .onUpdate("cascade")  // Referential action on update
  .name("owner")        // Custom relation name

Complete Example

const user = s.model({
  id: s.string().id().ulid(),
  email: s.string().unique(),
  // Non-owning side - no FK, can be optional
  profile: s.oneToOne(() => profile).optional(),
});

const profile = s.model({
  id: s.string().id().ulid(),
  bio: s.string().nullable(),
  avatar: s.string().nullable(),
  // FK field - unique for 1:1
  userId: s.string().unique(),
  // Owning side - has FK configuration
  user: s.oneToOne(() => user)
    .fields("userId")
    .references("id")
    .onDelete("cascade"),
});

Querying One-to-One

// Include profile when fetching user
const user = await client.user.findUnique({
  where: { id: "user_123" },
  include: { profile: true },
});
// user.profile: Profile | null

// Filter users by profile data
const users = await client.user.findMany({
  where: {
    profile: {
      is: { bio: { contains: "developer" } }
    }
  }
});

See To-One Relation Filters for all operators and Nested Writes for creating related records.

Required vs Optional

// Required: Every user MUST have a profile
// (Create profile in same transaction)
profile: s.oneToOne(() => profile)

// Optional: User MAY have a profile
profile: s.oneToOne(() => profile).optional()

Was this page helpful?