Relations
Practical examples for one-to-one, one-to-many, many-to-many, self-relations, and compound references.
Relations
The generated client is designed around relation-aware inputs and selectors. These examples focus on the generated-client surface rather than low-level query models.
Ownership rule
For any direct relation, exactly one side owns the foreign key.
The owning side is the side with:
fields: [...]references: [...]
The non-owning side is usually the navigation field or list field.
One-to-one
One-to-one
model User {
id Int @id @default(autoincrement())
email String @unique
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
bio String?
userId Int @unique
user User @relation(fields: [userId], references: [id])
}final user = await db.user.create(
data: const UserCreateInput(
email: 'alice@example.com',
profile: ProfileCreateNestedOneWithoutUserInput(
create: ProfileCreateWithoutUserInput(
bio: 'Ships docs and migrations',
),
),
),
include: const UserInclude(profile: true),
);In one-to-one relations, the owning side usually needs a unique local field or field set.
One-to-many
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
userId Int
user User @relation(fields: [userId], references: [id])
}await db.user.create(
data: const UserCreateInput(
email: 'alice@example.com',
posts: PostCreateNestedManyWithoutUserInput(
create: <PostCreateWithoutUserInput>[
PostCreateWithoutUserInput(title: 'First post'),
PostCreateWithoutUserInput(title: 'Second post'),
],
),
),
);The foreign key lives on the Post side because Post owns userId and declares fields plus references.
Implicit many-to-many
model Post {
id Int @id @default(autoincrement())
title String
tags Tag[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}await db.post.create(
data: PostCreateInput(
title: 'Ship relation docs',
tags: TagCreateNestedManyWithoutPostsInput(
connect: <TagWhereUniqueInput>[
const TagWhereUniqueInput(name: 'docs'),
const TagWhereUniqueInput(name: 'orm'),
],
),
),
);Implicit many-to-many works when both sides are list relations. The storage table is managed by the provider adapters and migration flow.
Referential actions
comon_orm supports referential actions in @relation(...):
onDelete: CascadeonDelete: RestrictonDelete: NoActiononDelete: SetNullonDelete: SetDefault- the same set for
onUpdate
Example:
model Post {
id Int @id @default(autoincrement())
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
}Practical rule:
SetNullonly makes sense when the local relation field is nullableSetDefaultonly makes sense when the local relation field has a meaningful default
Explicit relation names
Add explicit relation names when:
- the same two models are connected by more than one relation
- a model relates to itself
model Employee {
id Int @id @default(autoincrement())
managerId Int?
manager Employee? @relation("ManagementChain", fields: [managerId], references: [id])
reports Employee[] @relation("ManagementChain")
}Self-relations and compound references
Self-relations require explicit naming when there is more than one endpoint on the same model pair. Compound references are supported end-to-end, including generated unique selectors for compound ids and compound uniques.
model Account {
tenantId Int
slug String
name String
@@id([tenantId, slug])
}
model Profile {
id Int @id @default(autoincrement())
tenantId Int
accountSlug String
account Account @relation(fields: [tenantId, accountSlug], references: [tenantId, slug])
}await db.profile.create(
data: ProfileCreateInput(
account: AccountCreateNestedOneWithoutProfilesInput(
connect: const AccountWhereUniqueInput.tenantIdSlug(
tenantId: 7,
slug: 'main',
),
),
),
);Querying through relations
Relation filters compose into normal WhereInput trees:
final users = await db.user.findMany(
where: UserWhereInput(
posts: PostListRelationFilter(
some: const PostWhereInput(
title: StringFilter(contains: 'docs'),
),
),
),
include: const UserInclude(posts: true),
);