prax-orm 0.11.0

A next-generation, type-safe ORM for Rust inspired by Prisma
Documentation
---
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">&lt;Model&gt;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 &mdash; 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>