---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';
const relationBasics = `// Relations connect models together
model User {
id Int @id @auto
posts Post[] // One-to-Many: User has many Posts
}
model Post {
id Int @id @auto
// Foreign key field
authorId Int
// Relation to User model
author User @relation(fields: [authorId], references: [id])
}
// Relations are always defined on BOTH sides:
// - One side has the foreign key field(s) + @relation attribute
// - Other side has the array or optional reference`;
const oneToOne = `// One-to-One: User has exactly one Profile
model User {
id Int @id @auto
email String @unique
profile Profile? // Optional: User might not have a profile
}
model Profile {
id Int @id @auto
bio String?
avatar String?
// Foreign key (must be @unique for 1:1)
userId Int @unique
user User @relation(fields: [userId], references: [id])
}
// Alternative: Profile ID is also the User ID
model UserWithProfile {
id Int @id @auto
name String
}
model ProfileByUserId {
// Use same ID as User (shared primary key)
userId Int @id
bio String?
user UserWithProfile @relation(fields: [userId], references: [id])
}`;
const oneToMany = `// One-to-Many: User has many Posts
model User {
id Int @id @auto
email String @unique
posts Post[] // Array indicates "many" side
}
model Post {
id Int @id @auto
title String
content String?
// "One" side has the foreign key
authorId Int
author User @relation(fields: [authorId], references: [id])
}
// One-to-Many with optional relationship
model Category {
id Int @id @auto
name String
posts Post[]
}
model PostWithCategory {
id Int @id @auto
title String
categoryId Int? // Optional foreign key
category Category? @relation(fields: [categoryId], references: [id])
}`;
const manyToMany = `// Many-to-Many: Posts have many Tags, Tags have many Posts
// Implicit join table (Prax manages it)
model Post {
id Int @id @auto
title String
tags Tag[] // Many tags per post
}
model Tag {
id Int @id @auto
name String @unique
posts Post[] // Many posts per tag
}
// Explicit join table (you manage it)
// Use when you need additional fields on the relationship
model PostTagExplicit {
// Composite primary key
postId Int
tagId Int
// Additional relationship data
addedAt DateTime @default(now())
addedById Int?
// Relations
post Post @relation(fields: [postId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@id([postId, tagId])
@@index([tagId])
}`;
const selfRelation = `// Self-relation: Comments can have replies (tree structure)
model Comment {
id Int @id @auto
content String
postId Int
// Self-relation for nested comments
parentId Int?
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id])
replies Comment[] @relation("CommentReplies")
}
// Self-relation: Users can follow other users
model User {
id Int @id @auto
name String
// Users I follow
following User[] @relation("UserFollows")
// Users following me
followers User[] @relation("UserFollows")
}
// Self-relation: Employee -> Manager hierarchy
model Employee {
id Int @id @auto
name String
managerId Int?
manager Employee? @relation("EmployeeManager", fields: [managerId], references: [id])
reports Employee[] @relation("EmployeeManager")
}`;
const multipleRelations = `// Multiple relations between the same models
model User {
id Int @id @auto
email String @unique
writtenPosts Post[] @relation("PostAuthor") // Posts I wrote
editedPosts Post[] @relation("PostEditor") // Posts I edited
likedPosts Post[] @relation("PostLikes") // Posts I liked
}
model Post {
id Int @id @auto
title String
content String?
// Different relations to User
authorId Int
author User @relation("PostAuthor", fields: [authorId], references: [id])
editorId Int?
editor User? @relation("PostEditor", fields: [editorId], references: [id])
likedBy User[] @relation("PostLikes") // Many-to-many
}`;
const refActions = `// Referential actions control cascading behavior
model User {
id Int @id @auto
posts Post[]
}
model Post {
id Int @id @auto
authorId Int
author User @relation(
fields: [authorId],
references: [id],
onDelete: Cascade, // Delete posts when user is deleted
onUpdate: Cascade // Update FK when user ID changes
)
}
// All referential actions
model Example {
parentId Int
parent Parent @relation(
fields: [parentId],
references: [id],
onDelete: Cascade, // Delete this when parent deleted
// onDelete: Restrict, // Prevent parent deletion if this exists
// onDelete: SetNull, // Set FK to NULL (field must be optional)
// onDelete: SetDefault,// Set FK to default value
// onDelete: NoAction, // Database default (usually error)
onUpdate: Cascade
)
}`;
const compositeRelations = `// Relation using composite foreign key
model TenantUser {
tenantId Int
id Int
email String
posts TenantPost[]
@@id([tenantId, id])
}
model TenantPost {
tenantId Int
id Int
title String
// Composite foreign key
authorTenant Int
authorId Int
author TenantUser @relation(
fields: [authorTenant, authorId],
references: [tenantId, id]
)
@@id([tenantId, id])
@@index([authorTenant, authorId])
}`;
const queryingRelations = `use prax::generated::{user, post, include};
// Include related data (eager loading)
let user_with_posts = client
.user()
.find_unique()
.where(user::id::equals(1))
.include(user::posts::fetch())
.exec()
.await?;
// Nested includes
let user_with_full_posts = client
.user()
.find_unique()
.where(user::id::equals(1))
.include(user::posts::fetch()
.include(post::tags::fetch())
.include(post::comments::fetch()))
.exec()
.await?;
// Filter by related records
let users_with_published = client
.user()
.find_many()
.where(user::posts::some(post::published::equals(true)))
.exec()
.await?;
// Filter: all, some, none, is, isNot
let authors = client
.user()
.find_many()
.where(user::posts::some(post::likes::gt(100))) // Has popular post
.where(user::profile::is(profile::verified::equals(true))) // Verified
.exec()
.await?;`;
const nestedWrites = `use prax_query::data;
// Create with nested relation
let user = client
.user()
.create(data! {
email: "alice@example.com",
name: "Alice",
// Create related profile
profile: {
create: {
bio: "Software engineer",
avatar: "https://example.com/alice.jpg"
}
},
// Create multiple posts
posts: {
create: [
{ title: "Hello World", content: "My first post" },
{ title: "Second Post", published: true }
]
}
})
.exec()
.await?;
// Connect to existing records
let post = client
.post()
.create(data! {
title: "New Post",
author: {
connect: { id: 1 }
},
tags: {
connect: [{ id: 1 }, { id: 2 }]
}
})
.exec()
.await?;
// Disconnect relations
let post = client
.post()
.update()
.where(post::id::equals(1))
.data(data! {
tags: {
disconnect: [{ id: 3 }]
}
})
.exec()
.await?;`;
const bestPractices = `// ✅ Good: Clear naming for relation fields
model User {
id Int @id @auto
posts Post[] @relation("AuthoredPosts")
favoriteBooks Book[] @relation("FavoriteBooks")
}
// ✅ Good: Index foreign key columns
model Post {
id Int @id @auto
authorId Int
author User @relation(fields: [authorId], references: [id])
@@index([authorId]) // Important for query performance!
}
// ✅ Good: Use Cascade carefully
model UserSession {
id Int @id @auto
userId Int
user User @relation(
fields: [userId],
references: [id],
onDelete: Cascade // Sessions deleted when user deleted
)
}
// ⚠️ Careful: Restrict for important data
model Order {
id Int @id @auto
customerId Int
customer Customer @relation(
fields: [customerId],
references: [id],
onDelete: Restrict // Can't delete customer with orders
)
}`;
---
<DocsLayout title="Relations - Prax ORM">
<article class="max-w-4xl mx-auto px-6 py-12">
<header class="mb-12">
<h1 class="text-4xl font-bold mb-4">Relations</h1>
<p class="text-xl text-muted">
Define relationships between your models for powerful data querying and type-safe nested operations.
</p>
</header>
<div class="space-y-12">
<!-- Introduction -->
<section>
<h2 class="text-2xl font-semibold mb-4">Understanding Relations</h2>
<p class="text-muted mb-4">
Relations define how models connect to each other. They create foreign key constraints
in the database and enable type-safe queries with nested data loading in your Rust code.
</p>
<CodeBlock code={relationBasics} lang="prax" filename="prax/schema.prax" />
<div class="mt-6 grid md:grid-cols-3 gap-4">
<div class="p-4 rounded-xl bg-surface border border-border text-center">
<div class="text-3xl mb-2">1:1</div>
<h4 class="font-semibold mb-1">One-to-One</h4>
<p class="text-muted text-sm">User ↔ Profile</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border text-center">
<div class="text-3xl mb-2">1:N</div>
<h4 class="font-semibold mb-1">One-to-Many</h4>
<p class="text-muted text-sm">User → Posts[]</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border text-center">
<div class="text-3xl mb-2">M:N</div>
<h4 class="font-semibold mb-1">Many-to-Many</h4>
<p class="text-muted text-sm">Posts[] ↔ Tags[]</p>
</div>
</div>
</section>
<!-- One-to-One -->
<section>
<h2 class="text-2xl font-semibold mb-4">One-to-One Relations</h2>
<p class="text-muted mb-4">
A one-to-one relation means each record in one model has exactly one related record in another model.
The foreign key field must have a <code class="px-2 py-1 bg-surface-elevated rounded">@unique</code> constraint.
</p>
<CodeBlock code={oneToOne} lang="prax" filename="prax/schema.prax" />
<div class="mt-4 p-4 rounded-xl bg-info-500/10 border border-info-500/30">
<p class="text-info-400 text-sm">
<strong>Tip:</strong> Use one-to-one relations to split large models or when optional extended data
is only needed in certain contexts.
</p>
</div>
</section>
<!-- One-to-Many -->
<section>
<h2 class="text-2xl font-semibold mb-4">One-to-Many Relations</h2>
<p class="text-muted mb-4">
The most common relation type. One record can have many related records, but each related
record belongs to exactly one parent.
</p>
<CodeBlock code={oneToMany} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Many-to-Many -->
<section>
<h2 class="text-2xl font-semibold mb-4">Many-to-Many Relations</h2>
<p class="text-muted mb-4">
Many-to-many relations allow records in both models to have multiple related records.
Prax can manage the join table automatically, or you can define it explicitly.
</p>
<CodeBlock code={manyToMany} lang="prax" filename="prax/schema.prax" />
<div class="mt-4 p-4 rounded-xl bg-warning-500/10 border border-warning-500/30">
<p class="text-warning-400 text-sm">
<strong>When to use explicit join tables:</strong> When you need additional data on the relationship
(timestamps, ordering, metadata) or need more control over the join table structure.
</p>
</div>
</section>
<!-- Self-Relations -->
<section>
<h2 class="text-2xl font-semibold mb-4">Self-Relations</h2>
<p class="text-muted mb-4">
Self-relations allow a model to relate to itself. Common for hierarchical data like
comments with replies, organizational structures, or social graphs.
</p>
<CodeBlock code={selfRelation} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Multiple Relations -->
<section>
<h2 class="text-2xl font-semibold mb-4">Multiple Relations Between Models</h2>
<p class="text-muted mb-4">
When models have multiple relations, use the <code class="px-2 py-1 bg-surface-elevated rounded">name</code>
argument in <code class="px-2 py-1 bg-surface-elevated rounded">@relation</code> to distinguish them.
</p>
<CodeBlock code={multipleRelations} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Referential Actions -->
<section>
<h2 class="text-2xl font-semibold mb-4">Referential Actions</h2>
<p class="text-muted mb-4">
Control what happens to related records when a parent record is deleted or updated.
</p>
<CodeBlock code={refActions} lang="prax" filename="prax/schema.prax" />
<div class="mt-6 overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-3 px-4 font-semibold">Action</th>
<th class="text-left py-3 px-4 font-semibold">On Delete</th>
<th class="text-left py-3 px-4 font-semibold">On Update</th>
</tr>
</thead>
<tbody class="text-muted">
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">Cascade</code></td>
<td class="py-3 px-4">Delete related records</td>
<td class="py-3 px-4">Update foreign key values</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">Restrict</code></td>
<td class="py-3 px-4">Prevent deletion if related records exist</td>
<td class="py-3 px-4">Prevent update if related records exist</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">SetNull</code></td>
<td class="py-3 px-4">Set foreign key to NULL</td>
<td class="py-3 px-4">Set foreign key to NULL</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">SetDefault</code></td>
<td class="py-3 px-4">Set foreign key to default value</td>
<td class="py-3 px-4">Set foreign key to default value</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">NoAction</code></td>
<td class="py-3 px-4">Database default (usually error)</td>
<td class="py-3 px-4">Database default</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- Composite Relations -->
<section>
<h2 class="text-2xl font-semibold mb-4">Composite Foreign Keys</h2>
<p class="text-muted mb-4">
Relations can reference multiple fields for composite primary keys:
</p>
<CodeBlock code={compositeRelations} lang="prax" filename="prax/schema.prax" />
</section>
<!-- Querying Relations -->
<section>
<h2 class="text-2xl font-semibold mb-4">Querying Relations</h2>
<p class="text-muted mb-4">
Load related data with <code class="px-2 py-1 bg-surface-elevated rounded">include()</code>
and filter by related records:
</p>
<CodeBlock code={queryingRelations} lang="rust" filename="src/main.rs" />
</section>
<!-- Nested Writes -->
<section>
<h2 class="text-2xl font-semibold mb-4">Nested Writes</h2>
<p class="text-muted mb-4">
Create, connect, and disconnect related records in a single operation:
</p>
<CodeBlock code={nestedWrites} lang="rust" filename="src/main.rs" />
</section>
<!-- Best Practices -->
<section>
<h2 class="text-2xl font-semibold mb-4">Best Practices</h2>
<CodeBlock code={bestPractices} lang="prax" filename="prax/schema.prax" />
<div class="mt-6 grid gap-4">
<div class="p-4 rounded-xl bg-surface border border-border">
<h4 class="font-semibold mb-2 text-primary-400">Always Index Foreign Keys</h4>
<p class="text-muted text-sm">
Add <code class="px-1 bg-surface-elevated rounded">@@index([foreignKeyField])</code>
to improve JOIN and filter query performance.
</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<h4 class="font-semibold mb-2 text-primary-400">Choose Referential Actions Carefully</h4>
<p class="text-muted text-sm">
Use <code class="px-1 bg-surface-elevated rounded">Cascade</code> for child records that make no sense without the parent.
Use <code class="px-1 bg-surface-elevated rounded">Restrict</code> for important data that shouldn't be accidentally deleted.
</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<h4 class="font-semibold mb-2 text-primary-400">Name Relations for Clarity</h4>
<p class="text-muted text-sm">
Always use the <code class="px-1 bg-surface-elevated rounded">name</code> argument when models have multiple relations
to clearly distinguish their purpose.
</p>
</div>
</div>
</section>
<!-- Relation Attributes Reference -->
<section>
<h2 class="text-2xl font-semibold mb-4">Relation Attributes Reference</h2>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-3 px-4 font-semibold">Argument</th>
<th class="text-left py-3 px-4 font-semibold">Required</th>
<th class="text-left py-3 px-4 font-semibold">Description</th>
</tr>
</thead>
<tbody class="text-muted">
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">fields</code></td>
<td class="py-3 px-4">Yes*</td>
<td class="py-3 px-4">Foreign key field(s) on this model</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">references</code></td>
<td class="py-3 px-4">Yes*</td>
<td class="py-3 px-4">Referenced field(s) on the related model</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">name</code></td>
<td class="py-3 px-4">No</td>
<td class="py-3 px-4">Relation name (required for multiple relations)</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">onDelete</code></td>
<td class="py-3 px-4">No</td>
<td class="py-3 px-4">Action when parent is deleted</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code class="text-primary-400">onUpdate</code></td>
<td class="py-3 px-4">No</td>
<td class="py-3 px-4">Action when parent key is updated</td>
</tr>
</tbody>
</table>
</div>
<p class="text-muted text-sm mt-4">
* Required on the side of the relation that holds the foreign key
</p>
</section>
</div>
</article>
</DocsLayout>