---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';
const connectionExample = `// prax.toml configuration
[database]
provider = "mongodb"
url = "mongodb://localhost:27017/mydb"
# Replica set configuration
# url = "mongodb://primary:27017,secondary1:27017,secondary2:27017/mydb?replicaSet=rs0"
# MongoDB Atlas
# url = "mongodb+srv://user:password@cluster.mongodb.net/mydb"
[database.options]
app_name = "my-app"
max_pool_size = 10
min_pool_size = 2
connect_timeout = "10s"
server_selection_timeout = "30s"`;
const schemaExample = `// MongoDB schema definition
generator client {
provider = "prax-mongodb"
output = "./generated"
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
name String?
profile Profile? // Embedded document
tags String[] // Array field
metadata Json? // Flexible JSON/BSON
createdAt DateTime @default(now())
@@map("users")
}
// Embedded document type
type Profile {
bio String?
avatar String?
social SocialLinks?
}
type SocialLinks {
twitter String?
github String?
linkedin String?
}`;
const aggregationViewExample = `use prax_mongodb::view::{AggregationView, stages, accumulators};
// Define an aggregation view
let user_stats = AggregationView::new("user_stats")
.source("users")
.pipeline([
stages::lookup("posts", "id", "author_id", "user_posts"),
stages::unwind("$user_posts", true),
stages::group(
"$_id",
[
("email", accumulators::first("$email")),
("name", accumulators::first("$name")),
("post_count", accumulators::sum(1)),
("total_likes", accumulators::sum("$user_posts.likes")),
]
),
stages::sort([("total_likes", -1)]),
]);
// Materialize with $merge
let materialized = user_stats
.materialize("user_stats_cache")
.on_match(MergeAction::Replace)
.on_not_matched(MergeAction::Insert);`;
const changeStreamExample = `use prax::trigger::{ChangeStreamBuilder, ChangeType, ChangeStreamOptions};
// Watch for changes on a collection
let stream = ChangeStreamBuilder::new("users")
.watch_events([ChangeType::Insert, ChangeType::Update, ChangeType::Delete])
.filter(doc! {
"fullDocument.status": "active"
})
.full_document(FullDocumentType::UpdateLookup)
.build();
// Process changes
while let Some(change) = stream.next().await {
match change.operation_type {
ChangeType::Insert => {
println!("New user: {:?}", change.full_document);
}
ChangeType::Update => {
println!("Updated fields: {:?}", change.update_description);
}
ChangeType::Delete => {
println!("Deleted: {:?}", change.document_key);
}
}
}`;
const shardingExample = `use prax::partition::mongodb::{ShardKey, ZoneShardingBuilder};
// Define shard key for a collection
let shard_key = ShardKey::builder()
.hashed("tenant_id") // Hashed sharding for even distribution
.range("created_at") // Range for time-series queries
.build();
// Enable sharding
let command = shard_key.enable_sharding_command("orders");
// Zone sharding for geographic distribution
let zones = ZoneShardingBuilder::new("orders")
.add_zone("US", "tenant_id", "us_", "us_~")
.add_zone("EU", "tenant_id", "eu_", "eu_~")
.add_zone("APAC", "tenant_id", "apac_", "apac_~")
.build();`;
const atlasSearchExample = `use prax::search::mongodb::{AtlasSearchQuery, AtlasSearchIndexBuilder};
// Create Atlas Search index
let index = AtlasSearchIndexBuilder::new("default")
.collection("products")
.dynamic_mapping(true)
.field("name", "string", [("analyzer", "lucene.standard")])
.field("description", "string", [("analyzer", "lucene.english")])
.field("price", "number")
.field("location", "geo")
.build();
// Full-text search with Atlas Search
let results = AtlasSearchQuery::new("wireless headphones")
.index("default")
.path(["name", "description"])
.fuzzy(1, 3) // maxEdits, prefixLength
.highlight(["name", "description"])
.score_boost("name", 2.0)
.filter(doc! { "price": { "$lt": 200 } })
.limit(20)
.exec(&client)
.await?;
// Access highlights
for result in results {
println!("Score: {}", result.score);
for highlight in result.highlights {
println!("Match in {}: {}", highlight.path, highlight.texts.join("..."));
}
}`;
const vectorSearchExample = `use prax::extension::mongodb::{VectorSearch, VectorIndex};
// Create vector search index
let index = VectorIndex::new("embedding_index")
.collection("products")
.field("embedding", 1536) // OpenAI embedding dimension
.similarity("cosine")
.num_candidates(100)
.build();
// Vector similarity search
let query_embedding = get_embedding("wireless bluetooth headphones").await?;
let similar = VectorSearch::new("embedding_index")
.vector(query_embedding)
.path("embedding")
.limit(10)
.filter(doc! { "category": "electronics" })
.exec(&client)
.await?;`;
const documentOpsExample = `use prax::json::mongodb::{UpdateOp, ArrayOp, UpdateBuilder};
// Atomic document updates
let update = UpdateBuilder::new()
.set("profile.bio", "Software Engineer")
.set("updatedAt", Bson::DateTime(now()))
.inc("loginCount", 1)
.push("tags", "verified")
.add_to_set("roles", "admin")
.unset("tempField")
.build();
client.users().update_one(
doc! { "_id": user_id },
update
).await?;
// Array operations
let array_update = UpdateBuilder::new()
.push_each("scores", [85, 90, 95])
.pull("tags", "unverified")
.pop("notifications", -1) // Remove first element
.build();
// Positional updates
let positional = UpdateBuilder::new()
.set("items.$.quantity", 5) // Update matched array element
.build();
client.orders().update_one(
doc! { "_id": order_id, "items.productId": product_id },
positional
).await?;`;
const readPreferenceExample = `use prax::replication::mongodb::{MongoReadPreference, ReadConcern, WriteConcern};
// Configure read preference
let read_pref = MongoReadPreference::secondary_preferred()
.max_staleness(Duration::from_secs(90))
.tag_set([("region", "us-east-1"), ("type", "analytics")])
.hedged(true); // Send to multiple replicas, use first response
// Read concern levels
let concern = ReadConcern::Majority; // Read from majority-committed data
// Options: Local, Available, Majority, Linearizable, Snapshot
// Write concern
let write_concern = WriteConcern::Majority
.journal(true)
.timeout(Duration::from_secs(5));
// Apply to operations
let users = client
.users()
.with_read_preference(read_pref)
.with_read_concern(ReadConcern::Majority)
.find_many()
.exec()
.await?;`;
const fieldEncryptionExample = `use prax::security::mongodb::{FieldEncryption, KmsProvider};
// Configure Client-Side Field Level Encryption (CSFLE)
let encryption = FieldEncryption::new()
.kms_provider(KmsProvider::Aws {
access_key_id: env!("AWS_ACCESS_KEY_ID"),
secret_access_key: env!("AWS_SECRET_ACCESS_KEY"),
region: "us-east-1",
})
.key_vault("encryption.__keyVault")
.schema_map("users", doc! {
"bsonType": "object",
"encryptMetadata": {
"keyId": "/keyAltName"
},
"properties": {
"ssn": {
"encrypt": {
"bsonType": "string",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
}
},
"medicalRecords": {
"encrypt": {
"bsonType": "array",
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
}
}
}
});
// Client with encryption enabled
let client = PraxClient::mongodb()
.url(db_url)
.encryption(encryption)
.connect()
.await?;`;
const atlasTriggerExample = `use prax_migrate::procedure::{AtlasTrigger, AtlasTriggerType, AtlasOperation};
// Database trigger (Atlas only)
let trigger = AtlasTrigger::new("user_signup_handler")
.trigger_type(AtlasTriggerType::Database)
.collection("users")
.operations([AtlasOperation::Insert])
.full_document(true)
.function_name("onUserSignup");
// Scheduled trigger
let scheduled = AtlasTrigger::new("daily_report")
.trigger_type(AtlasTriggerType::Scheduled)
.schedule("0 0 * * *") // Cron: Daily at midnight
.function_name("generateDailyReport");
// Authentication trigger
let auth_trigger = AtlasTrigger::new("on_user_create")
.trigger_type(AtlasTriggerType::Authentication)
.operation_type("CREATE")
.function_name("initializeUserProfile");`;
---
<DocsLayout title="MongoDB - 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">MongoDB</h1>
<p class="text-xl text-muted">
Native MongoDB support with aggregation pipelines, change streams, Atlas Search, vector search, and CSFLE encryption.
</p>
</header>
<div class="space-y-12">
<!-- Introduction -->
<section>
<h2 class="text-2xl font-semibold mb-4">Overview</h2>
<p class="text-muted mb-4">
Prax provides first-class MongoDB support through the official Rust driver, bringing type-safe
document operations while preserving MongoDB's flexible schema capabilities.
</p>
<div class="grid md:grid-cols-3 gap-4">
<div class="p-4 rounded-xl bg-surface border border-border">
<div class="text-2xl mb-2">🔄</div>
<h4 class="font-semibold mb-1">Change Streams</h4>
<p class="text-muted text-sm">Real-time data change notifications</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<div class="text-2xl mb-2">🔍</div>
<h4 class="font-semibold mb-1">Atlas Search</h4>
<p class="text-muted text-sm">Full-text and vector search</p>
</div>
<div class="p-4 rounded-xl bg-surface border border-border">
<div class="text-2xl mb-2">🔐</div>
<h4 class="font-semibold mb-1">CSFLE</h4>
<p class="text-muted text-sm">Client-side field-level encryption</p>
</div>
</div>
</section>
<!-- Connection -->
<section>
<h2 class="text-2xl font-semibold mb-4">Connection Configuration</h2>
<p class="text-muted mb-4">
Configure your MongoDB connection in <code class="px-2 py-1 bg-surface-elevated rounded">prax.toml</code>.
Supports standalone, replica sets, and MongoDB Atlas.
</p>
<CodeBlock code={connectionExample} lang="toml" filename="prax.toml" />
</section>
<!-- Schema -->
<section>
<h2 class="text-2xl font-semibold mb-4">Schema Definition</h2>
<p class="text-muted mb-4">
Define your document structure with embedded documents and arrays.
Use <code class="px-2 py-1 bg-surface-elevated rounded">type</code> for embedded subdocuments.
</p>
<CodeBlock code={schemaExample} 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>Collection names:</strong> In v0.11, the engine resolves each model's collection
from the declared table name — <code>@@map("...")</code> in the schema or
<code>#[prax(table = "...")]</code> in Rust (<code>Model::TABLE_NAME</code>), consistent
with every SQL engine. This replaces the old naive pluralization heuristic (which turned
<code>Category</code> into <code>categorys</code>). If your collections were named by the
old heuristic, rename them in MongoDB or pin the existing name with <code>@@map</code>.
</p>
</div>
<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>Filtering on <code>_id</code>:</strong> when a filter targets <code>_id</code>,
24-character hex strings are coerced to <code>ObjectId</code> so they match stored IDs.
A 24-hex string filtered on any <em>other</em> field is compared as a plain string.
</p>
</div>
</section>
<!-- Aggregation Views -->
<section>
<h2 class="text-2xl font-semibold mb-4">Aggregation Views</h2>
<p class="text-muted mb-4">
Create views backed by aggregation pipelines. Materialize them with
<code class="px-2 py-1 bg-surface-elevated rounded">$merge</code> or
<code class="px-2 py-1 bg-surface-elevated rounded">$out</code>.
</p>
<CodeBlock code={aggregationViewExample} lang="rust" filename="src/views.rs" />
</section>
<!-- Change Streams -->
<section>
<h2 class="text-2xl font-semibold mb-4">Change Streams</h2>
<p class="text-muted mb-4">
Watch for real-time changes to your collections. Requires a replica set or sharded cluster.
</p>
<CodeBlock code={changeStreamExample} lang="rust" filename="src/main.rs" />
<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>Note:</strong> Change streams require a replica set. For local development,
run MongoDB with <code>--replSet rs0</code> and initialize the replica set.
</p>
</div>
</section>
<!-- Sharding -->
<section>
<h2 class="text-2xl font-semibold mb-4">Sharding</h2>
<p class="text-muted mb-4">
Configure shard keys and zone sharding for horizontal scaling.
</p>
<CodeBlock code={shardingExample} lang="rust" filename="src/sharding.rs" />
</section>
<!-- Atlas Search -->
<section>
<h2 class="text-2xl font-semibold mb-4">Atlas Search</h2>
<p class="text-muted mb-4">
Full-text search with Lucene-powered Atlas Search. Includes fuzzy matching, highlighting, and scoring.
</p>
<CodeBlock code={atlasSearchExample} lang="rust" filename="src/search.rs" />
</section>
<!-- Vector Search -->
<section>
<h2 class="text-2xl font-semibold mb-4">Vector Search</h2>
<p class="text-muted mb-4">
Semantic similarity search with Atlas Vector Search for AI/ML applications.
</p>
<CodeBlock code={vectorSearchExample} lang="rust" filename="src/vectors.rs" />
</section>
<!-- Document Operations -->
<section>
<h2 class="text-2xl font-semibold mb-4">Document Operations</h2>
<p class="text-muted mb-4">
Type-safe atomic updates with MongoDB's rich update operators.
</p>
<CodeBlock code={documentOpsExample} lang="rust" filename="src/main.rs" />
</section>
<!-- Read Preference -->
<section>
<h2 class="text-2xl font-semibold mb-4">Read Preference & Concerns</h2>
<p class="text-muted mb-4">
Fine-grained control over read/write distribution in replica sets.
</p>
<CodeBlock code={readPreferenceExample} lang="rust" filename="src/config.rs" />
<div class="mt-4 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">Read Preference</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>Primary</code></td>
<td class="py-3 px-4">Read from primary only (default)</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>PrimaryPreferred</code></td>
<td class="py-3 px-4">Primary if available, otherwise secondary</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>Secondary</code></td>
<td class="py-3 px-4">Read from secondaries only</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>SecondaryPreferred</code></td>
<td class="py-3 px-4">Secondary if available, otherwise primary</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4"><code>Nearest</code></td>
<td class="py-3 px-4">Lowest latency member</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- Field Encryption -->
<section>
<h2 class="text-2xl font-semibold mb-4">Client-Side Field Level Encryption</h2>
<p class="text-muted mb-4">
Encrypt sensitive fields before they leave your application with CSFLE.
</p>
<CodeBlock code={fieldEncryptionExample} lang="rust" filename="src/security.rs" />
</section>
<!-- Atlas Triggers -->
<section>
<h2 class="text-2xl font-semibold mb-4">Atlas Triggers</h2>
<p class="text-muted mb-4">
Define database, scheduled, and authentication triggers for MongoDB Atlas.
</p>
<CodeBlock code={atlasTriggerExample} lang="rust" filename="migrations/triggers.rs" />
</section>
<!-- Feature Support -->
<section>
<h2 class="text-2xl font-semibold mb-4">MongoDB Feature Support</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">Feature</th>
<th class="text-left py-3 px-4 font-semibold">Status</th>
<th class="text-left py-3 px-4 font-semibold">Notes</th>
</tr>
</thead>
<tbody class="text-muted">
<tr class="border-b border-border">
<td class="py-3 px-4">Aggregation Pipelines</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">Full stage support</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Change Streams</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">Real-time updates</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Atlas Search</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">Full-text search</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Vector Search</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">Atlas only</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Sharding</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">Hashed & range</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Zone Sharding</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">Geographic distribution</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">CSFLE</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">AWS, Azure, GCP KMS</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Atlas Triggers</td>
<td class="py-3 px-4"><span class="text-success-400">✅</span></td>
<td class="py-3 px-4">Database/Scheduled/Auth</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Schema Inference</td>
<td class="py-3 px-4"><span class="text-muted">⏳</span></td>
<td class="py-3 px-4">Deferred — <code>prax db pull</code> is PostgreSQL-only in v0.11</td>
</tr>
<tr class="border-b border-border">
<td class="py-3 px-4">Nested Writes</td>
<td class="py-3 px-4"><span class="text-muted">❌</span></td>
<td class="py-3 px-4">Not supported at engine level (<code>MongoEngine</code> does not implement <code>SupportsNestedWrites</code>)</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>
</article>
</DocsLayout>