---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';
const createCode = `// Create a single record — set columns with .set() chaining
let user = client
.user()
.create()
.set("email", "alice@example.com")
.set("name", "Alice")
.set("age", 30)
.exec()
.await?;
// Or apply the generated typed input (UserCreateInput)
let user = client
.user()
.create()
.with_create_input(UserCreateInput {
email: "alice@example.com".into(),
name: Some("Alice".into()),
..Default::default()
})
.exec()
.await?;
// Create many records
let count = client
.user()
.create_many()
.with_create_inputs([
UserCreateInput { email: "bob@example.com".into(), ..Default::default() },
UserCreateInput { email: "charlie@example.com".into(), ..Default::default() },
])
.exec()
.await?;
// Skip duplicates
let count = client
.user()
.create_many()
.with_create_inputs([...])
.skip_duplicates()
.exec()
.await?;`;
const createBuilder = `// The operation itself is the builder: chain .set() per column,
// or .set_many() with (column, value) pairs
let user = client
.user()
.create()
.set("email", "alice@example.com")
.set_many([
("name", "Alice"),
("city", "Portland"),
])
.exec()
.await?;`;
const readCode = `// Find unique by ID
let user = client
.user()
.find_unique()
.where(user::id::equals(1))
.exec()
.await?;
// Find first matching
let user = client
.user()
.find_first()
.where(user::email::contains("@example.com"))
.exec()
.await?;
// Find many
let users = client
.user()
.find_many()
.where(user::active::equals(true))
.exec()
.await?;`;
const updateCode = `// Update with .set() calls
let user = client
.user()
.update()
.where(user::id::equals(1))
.set("name", "Alice Updated")
.set("updated_at", chrono::Utc::now().to_rfc3339())
.exec()
.await?;
// Numeric operations
let user = client
.user()
.update()
.where(user::id::equals(1))
.increment("views", 1) // SET views = views + 1
.increment("login_count", 1)
.exec()
.await?;
// Update many
let count = client
.user()
.update_many()
.where(user::active::equals(false))
.set("deleted_at", chrono::Utc::now().to_rfc3339())
.exec()
.await?;`;
const deleteCode = `// Delete unique
let user = client
.user()
.delete()
.where(user::id::equals(1))
.exec()
.await?;
// Delete many
let count = client
.user()
.delete_many()
.where(user::active::equals(false))
.exec()
.await?;`;
const upsertCode = `// Create or update
let user = client
.user()
.upsert()
.where(user::email::equals("alice@example.com"))
.create_set("email", "alice@example.com")
.create_set("name", "Alice")
.update_set("name", "Alice (Updated)")
.exec()
.await?;
// Bulk forms: (column, value) pairs or the generated typed inputs
// .create([("email", "alice@example.com"), ("name", "Alice")])
// .with_create_input(UserCreateInput { ... })
// .with_update_input(UserUpdateInput { ... })`;
const relationCreate = `// Create with relation connection — .with() queues a nested
// write that runs in the same transaction as the parent INSERT
let post = client
.post()
.create()
.set("title", "Hello World")
.set("content", "My first post!")
.with(post::author::connect(1)) // Connect to existing user
.exec()
.await?;
// Create with nested creates — each child is a Vec of
// (column, value) pairs; the FK is filled in automatically
let user = client
.user()
.create()
.set("email", "alice@example.com")
.set("name", "Alice")
.with(user::posts::create(vec![
vec![
("title".to_string(), "Post 1".into()),
("content".to_string(), "...".into()),
],
vec![
("title".to_string(), "Post 2".into()),
("content".to_string(), "...".into()),
],
]))
.exec()
.await?;`;
---
<DocsLayout title="CRUD Operations - 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">CRUD Operations</h1>
<p class="text-xl text-muted">
Create, Read, Update, and Delete records with type-safe queries and ergonomic APIs.
</p>
</header>
<nav class="mb-8 p-4 bg-surface rounded-lg border border-border">
<h3 class="text-sm font-semibold text-muted mb-2">On this page</h3>
<ul class="flex flex-wrap gap-4 text-sm">
<li><a href="#create" class="text-primary-400 hover:text-primary-300">Create</a></li>
<li><a href="#read" class="text-primary-400 hover:text-primary-300">Read</a></li>
<li><a href="#update" class="text-primary-400 hover:text-primary-300">Update</a></li>
<li><a href="#delete" class="text-primary-400 hover:text-primary-300">Delete</a></li>
<li><a href="#upsert" class="text-primary-400 hover:text-primary-300">Upsert</a></li>
<li><a href="#relations" class="text-primary-400 hover:text-primary-300">Relations</a></li>
</ul>
</nav>
<div class="space-y-12">
<section id="create">
<h2 class="text-2xl font-semibold mb-4">Create</h2>
<p class="text-muted mb-4">
Set columns with <code class="text-primary-400">.set()</code> chaining, or apply the
generated <code class="text-primary-400"><Model>CreateInput</code> with
<code class="text-primary-400">.with_create_input(...)</code>.
</p>
<p class="text-muted mb-4">
Fields declared with a database default
(<code class="text-primary-400">#[prax(default = ...)]</code>, or
<code class="text-primary-400">@default</code> in the schema DSL) are Option-wrapped in
the generated CreateInput in v0.11, so they may be omitted — the database fills
them in.
</p>
<CodeBlock code={createCode} lang="rust" filename="Create Records" />
<div class="mt-6">
<h3 class="text-lg font-semibold mb-3">Builder Pattern</h3>
<p class="text-muted mb-4">
For more control, chain <code class="text-primary-400">.set()</code> /
<code class="text-primary-400">.set_many(...)</code> on the operation builder.
</p>
<CodeBlock code={createBuilder} lang="rust" filename="Builder Pattern" />
</div>
</section>
<section id="read">
<h2 class="text-2xl font-semibold mb-4">Read</h2>
<p class="text-muted mb-4">
Find records with type-safe queries.
</p>
<CodeBlock code={readCode} lang="rust" filename="Read Records" />
</section>
<section id="update">
<h2 class="text-2xl font-semibold mb-4">Update</h2>
<p class="text-muted mb-4">
Update records with field operations like increment, decrement, and more.
</p>
<CodeBlock code={updateCode} lang="rust" filename="Update Records" />
<div class="mt-4 p-4 bg-blue-900/30 border border-blue-700 rounded-lg">
<h4 class="font-semibold text-blue-400 mb-2">💡 Available Operations</h4>
<ul class="text-sm text-muted space-y-1">
<li>• <code class="text-primary-400">increment!(n)</code> - Add to numeric field</li>
<li>• <code class="text-primary-400">decrement!(n)</code> - Subtract from numeric field</li>
<li>• <code class="text-primary-400">.multiply("field", n)</code> - Multiply numeric field</li>
<li>• <code class="text-primary-400">.divide("field", n)</code> - Divide numeric field</li>
<li>• <code class="text-primary-400">.push("field", val)</code> - Append to array</li>
<li>• <code class="text-primary-400">.set_null("field")</code> - Set to NULL</li>
<li>• <code class="text-primary-400">.unset("field")</code> - Remove field</li>
</ul>
</div>
</section>
<section id="delete">
<h2 class="text-2xl font-semibold mb-4">Delete</h2>
<p class="text-muted mb-4">
Remove records safely with type-safe filters.
</p>
<CodeBlock code={deleteCode} lang="rust" filename="Delete Records" />
</section>
<section id="upsert">
<h2 class="text-2xl font-semibold mb-4">Upsert</h2>
<p class="text-muted mb-4">
Create a record if it doesn't exist, or update it if it does.
</p>
<CodeBlock code={upsertCode} lang="rust" filename="Upsert Records" />
</section>
<section id="relations">
<h2 class="text-2xl font-semibold mb-4">Relations</h2>
<p class="text-muted mb-4">
Connect to existing records or create nested records in a single operation.
</p>
<CodeBlock code={relationCreate} lang="rust" filename="Relations in Create" />
</section>
</div>
</article>
</DocsLayout>