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()