---
import DocsLayout from '../../layouts/DocsLayout.astro';
import CodeBlock from '../../components/CodeBlock.astro';
const basicExample = `use prax_query::middleware::{
BoxFuture, LoggingMiddleware, LogLevel, MetricsMiddleware, MiddlewareResult,
MiddlewareStack, QueryContext, QueryResponse, TimingMiddleware,
};
// Create a middleware stack
let stack = MiddlewareStack::new()
.with(LoggingMiddleware::new().with_level(LogLevel::Debug))
.with(TimingMiddleware::new())
.with(MetricsMiddleware::in_memory().0);
// There is no PraxClient::with_middleware — middleware composes at the
// prax-query level: run the stack around engine calls with a terminal
// handler that forwards the (possibly rewritten) query to the engine.
let response = stack
.execute(
QueryContext::new("SELECT * FROM users", vec![]),
|ctx| -> BoxFuture<'_, MiddlewareResult<QueryResponse>> {
Box::pin(async move {
let rows = engine
.query_many::<User>(ctx.sql(), ctx.params().to_vec())
.await?;
Ok(QueryResponse::new(serde_json::json!({ "rows": rows.len() })))
})
},
)
.await?;`;
const loggingMiddleware = `use prax_query::middleware::{LogLevel, LoggingMiddleware};
// Builder-style setup
let logging = LoggingMiddleware::new()
.with_level(LogLevel::Debug)
.with_params(true) // Log query parameters
.with_slow_threshold(500_000); // 500ms slow query warning
// Note: a LoggingConfig struct exists internally (with a
// LoggingMiddleware::with_config(..) constructor) but the type is not
// currently re-exported from prax_query::middleware — use the with_*
// methods above.`;
const metricsMiddleware = `use prax_query::middleware::MetricsMiddleware;
// Create an in-memory collector + middleware pair.
// (InMemoryMetricsCollector itself is not re-exported from
// prax_query::middleware — in_memory() is the way in.)
let (metrics, collector) = MetricsMiddleware::in_memory();
// Use the middleware...
// Later, get metrics
let stats = collector.get_metrics();
println!("Total queries: {}", stats.total_queries);
println!("Success rate: {:.2}%", stats.success_rate() * 100.0);
println!("Average time: {}μs", stats.avg_time_us);
println!("Slow queries: {}", stats.slow_queries);
println!("Cache hits: {}", stats.cache_hits);
// Queries by type
for (query_type, count) in &stats.queries_by_type {
println!(" {}: {}", query_type, count);
}`;
const retryMiddleware = `use prax_query::middleware::{RetryConfig, RetryMiddleware};
use std::time::Duration;
// Basic retry with defaults (3 retries, exponential backoff)
let retry = RetryMiddleware::default_config();
// Custom retry configuration
let retry = RetryMiddleware::new(
RetryConfig::new()
.max_retries(5)
.initial_delay(Duration::from_millis(100))
.max_delay(Duration::from_secs(10))
.backoff_multiplier(2.0)
.jitter(true) // Add randomness to prevent thundering herd
);
// (RetryConfig::retry_on(..) accepts a RetryPredicate, but
// RetryPredicate / RetryableError are not currently re-exported from
// prax_query::middleware.)`;
const customMiddleware = `use prax_query::middleware::{
Middleware, QueryContext, QueryResponse, Next,
MiddlewareResult, BoxFuture
};
struct AuthMiddleware {
tenant_id: String,
}
impl Middleware for AuthMiddleware {
fn handle<'a>(
&'a self,
mut ctx: QueryContext,
next: Next<'a>,
) -> BoxFuture<'a, MiddlewareResult<QueryResponse>> {
Box::pin(async move {
// Add tenant context to all queries
ctx.metadata_mut().tenant_id = Some(self.tenant_id.clone());
// Modify SQL to add tenant filter (example)
if ctx.query_type().is_read() {
let sql = ctx.sql().to_string();
// Add tenant filtering logic...
}
// Continue to next middleware
next.run(ctx).await
})
}
fn name(&self) -> &'static str {
"AuthMiddleware"
}
}`;
const queryContext = `use prax_query::middleware::{QueryContext, QueryMetadata, QueryType};
// Query context provides information about the current query
fn inspect_context(ctx: &QueryContext) {
// Get SQL and parameters
println!("SQL: {}", ctx.sql());
println!("Params: {:?}", ctx.params());
// Query type detection
match ctx.query_type() {
QueryType::Select => println!("This is a SELECT query"),
QueryType::Insert => println!("This is an INSERT query"),
QueryType::Update => println!("This is an UPDATE query"),
QueryType::Delete => println!("This is a DELETE query"),
QueryType::Count => println!("This is a COUNT query"),
_ => println!("Other query type"),
}
// Check query characteristics
if ctx.is_read() {
println!("Read operation - safe for replicas");
}
if ctx.is_write() {
println!("Write operation - use primary");
}
// Timing
println!("Elapsed: {:?}", ctx.elapsed());
}
// Query metadata for tracing
let metadata = QueryMetadata::new()
.with_model("User")
.with_operation("findMany")
.with_request_id("req-abc123")
.with_user_id("user-456")
.with_tenant_id("tenant-789")
.with_tag("env", "production");`;
---
<DocsLayout title="Middleware System - 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">Middleware System</h1>
<p class="text-xl text-muted">
Prax provides a powerful middleware system for intercepting queries before and after execution.
Use middleware for logging, metrics, authentication, caching, retry logic, and more.
</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="space-y-1 text-sm">
<li><a href="#overview" class="text-primary-400 hover:text-primary-300">Overview</a></li>
<li><a href="#logging" class="text-primary-400 hover:text-primary-300">Logging Middleware</a></li>
<li><a href="#metrics" class="text-primary-400 hover:text-primary-300">Metrics Middleware</a></li>
<li><a href="#retry" class="text-primary-400 hover:text-primary-300">Retry Middleware</a></li>
<li><a href="#custom" class="text-primary-400 hover:text-primary-300">Custom Middleware</a></li>
<li><a href="#context" class="text-primary-400 hover:text-primary-300">Query Context</a></li>
</ul>
</nav>
<div class="space-y-12">
<section id="overview" class="mb-12">
<h2 class="text-2xl font-semibold mb-4">Overview</h2>
<p class="text-muted mb-4">
The middleware system allows you to intercept every query before and after execution.
Middleware can modify queries, add context, collect metrics, implement caching, and more.
</p>
<CodeBlock code={basicExample} lang="rust" filename="Basic Setup" />
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="p-4 bg-surface rounded-lg">
<h4 class="font-semibold text-primary-400 mb-2">Built-in Middleware</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• <code>LoggingMiddleware</code> - Query logging with levels</li>
<li>• <code>MetricsMiddleware</code> - Performance metrics</li>
<li>• <code>TimingMiddleware</code> - Execution time tracking</li>
<li>• <code>RetryMiddleware</code> - Automatic retry with backoff</li>
<li>• <code>TenantMiddleware</code> - Row-level tenant SQL filtering</li>
</ul>
</div>
<div class="p-4 bg-surface rounded-lg">
<h4 class="font-semibold text-primary-400 mb-2">Use Cases</h4>
<ul class="text-sm space-y-1 text-muted">
<li>• Query logging and debugging</li>
<li>• Performance monitoring</li>
<li>• Multi-tenant data isolation</li>
<li>• Query caching</li>
<li>• Circuit breaking</li>
</ul>
</div>
</div>
<div class="mt-4 p-4 bg-surface rounded-lg border border-border">
<h4 class="font-semibold text-primary-400 mb-2">TenantMiddleware (v0.11)</h4>
<p class="text-sm text-muted">
<code>TenantMiddleware</code> rewrites every query to enforce tenant isolation at the SQL
level. Tenant values are validated against the declared column type before interpolation,
an existing <code>WHERE</code> predicate is parenthesized so <code>OR</code> branches cannot
bypass the filter, fail-closed statement shapes (CTE-wrapped <code>SELECT</code>,
<code>MERGE</code>, <code>REPLACE</code>, …) are rejected loudly, <code>INSERT</code> gets the
tenant column auto-injected, and <code>UPDATE</code>/<code>DELETE</code> are validated to
reference the tenant column. Task-local resolution (<code>with_tenant</code>) takes
precedence over the shared middleware slot. See the
<a href="/advanced/multitenancy" class="text-primary-400 hover:text-primary-300">Multi-Tenancy page</a>
for details.
</p>
</div>
</section>
<section id="logging" class="mb-12">
<h2 class="text-2xl font-semibold mb-4">Logging Middleware</h2>
<p class="text-muted mb-4">
Log all queries with configurable detail levels. Automatically detect slow queries and log warnings.
</p>
<CodeBlock code={loggingMiddleware} lang="rust" filename="Logging Configuration" />
<div class="mt-6">
<h4 class="font-semibold mb-3">Log Levels</h4>
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border">
<th class="text-left py-2 text-muted">Level</th>
<th class="text-left py-2 text-muted">Description</th>
</tr>
</thead>
<tbody class="text-muted">
<tr class="border-b border-border/50">
<td class="py-2"><code>Off</code></td>
<td>No logging</td>
</tr>
<tr class="border-b border-border/50">
<td class="py-2"><code>Error</code></td>
<td>Only errors</td>
</tr>
<tr class="border-b border-border/50">
<td class="py-2"><code>Warn</code></td>
<td>Errors and slow queries</td>
</tr>
<tr class="border-b border-border/50">
<td class="py-2"><code>Info</code></td>
<td>All queries (default)</td>
</tr>
<tr class="border-b border-border/50">
<td class="py-2"><code>Debug</code></td>
<td>Queries with parameters</td>
</tr>
<tr class="border-b border-border/50">
<td class="py-2"><code>Trace</code></td>
<td>Everything including responses</td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="metrics" class="mb-12">
<h2 class="text-2xl font-semibold mb-4">Metrics Middleware</h2>
<p class="text-muted mb-4">
Collect query performance metrics for monitoring and optimization.
</p>
<CodeBlock code={metricsMiddleware} lang="rust" filename="Metrics Collection" />
<div class="mt-6">
<h4 class="font-semibold mb-3">Available Metrics</h4>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 text-sm">
<div class="p-3 bg-surface rounded">
<code class="text-primary-400">total_queries</code>
<p class="text-muted text-xs mt-1">Total query count</p>
</div>
<div class="p-3 bg-surface rounded">
<code class="text-primary-400">successful_queries</code>
<p class="text-muted text-xs mt-1">Successful count</p>
</div>
<div class="p-3 bg-surface rounded">
<code class="text-primary-400">failed_queries</code>
<p class="text-muted text-xs mt-1">Failed count</p>
</div>
<div class="p-3 bg-surface rounded">
<code class="text-primary-400">avg_time_us</code>
<p class="text-muted text-xs mt-1">Average time (μs)</p>
</div>
<div class="p-3 bg-surface rounded">
<code class="text-primary-400">slow_queries</code>
<p class="text-muted text-xs mt-1">Slow query count</p>
</div>
<div class="p-3 bg-surface rounded">
<code class="text-primary-400">cache_hits</code>
<p class="text-muted text-xs mt-1">Cache hit count</p>
</div>
</div>
</div>
</section>
<section id="retry" class="mb-12">
<h2 class="text-2xl font-semibold mb-4">Retry Middleware</h2>
<p class="text-muted mb-4">
Automatically retry failed queries with exponential backoff. Perfect for handling transient errors.
</p>
<CodeBlock code={retryMiddleware} lang="rust" filename="Retry Configuration" />
</section>
<section id="custom" class="mb-12">
<h2 class="text-2xl font-semibold mb-4">Custom Middleware</h2>
<p class="text-muted mb-4">
Create your own middleware by implementing the <code>Middleware</code> trait.
</p>
<CodeBlock code={customMiddleware} lang="rust" filename="Custom Middleware Example" />
</section>
<section id="context">
<h2 class="text-2xl font-semibold mb-4">Query Context</h2>
<p class="text-muted mb-4">
The <code class="px-2 py-1 bg-surface-elevated rounded">QueryContext</code> provides information about the current query and allows modification.
</p>
<CodeBlock code={queryContext} lang="rust" filename="Query Context" />
</section>
</div>
</article>
</DocsLayout>