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 parseUrl = `use prax_query::connection::{ConnectionString, Driver};

// Parse a connection URL
let conn = ConnectionString::parse(
    "postgres://user:pass@localhost:5432/mydb?sslmode=require"
)?;

// Access components
assert_eq!(conn.driver(), Driver::Postgres);
assert_eq!(conn.user(), Some("user"));
assert_eq!(conn.password(), Some("pass"));
assert_eq!(conn.host(), Some("localhost"));
assert_eq!(conn.port(), Some(5432));
assert_eq!(conn.database(), Some("mydb"));
assert_eq!(conn.param("sslmode"), Some("require"));

// From environment variable
let conn = ConnectionString::from_database_url()?;  // Uses DATABASE_URL
let conn = ConnectionString::from_env("MY_DB_URL")?;`;

const urlFormats = `// PostgreSQL
postgres://user:password@host:5432/database
postgresql://user:password@host:5432/database

// MySQL
mysql://user:password@host:3306/database
mariadb://user:password@host:3306/database

// SQLite
sqlite://./path/to/database.db
sqlite::memory:  // In-memory database

// With query parameters
postgres://user:pass@host/db?sslmode=require&connect_timeout=10`;

const builderPattern = `use prax_query::connection::{DatabaseConfig, SslMode};
use std::time::Duration;

// PostgreSQL configuration
let config = DatabaseConfig::postgres()
    .host("localhost")
    .port(5432)
    .database("mydb")
    .user("user")
    .password("pass")
    .ssl_mode(SslMode::Require)
    .connect_timeout(Duration::from_secs(10))
    .max_connections(20)
    .min_connections(5)
    .idle_timeout(Duration::from_secs(300))
    .application_name("my-app")
    .build()?;

// MySQL configuration
let config = DatabaseConfig::mysql()
    .host("localhost")
    .database("mydb")
    .user("root")
    .mysql_options(|opts| opts
        .charset("utf8mb4")
        .compression(true)
    )
    .build()?;

// SQLite configuration
let config = DatabaseConfig::sqlite()
    .database("./data/app.db")
    .sqlite_options(|opts| opts
        .foreign_keys(true)
        .busy_timeout(5000)
    )
    .build()?;
// Note: SqliteOptions also has journal_mode(..) / synchronous(..), but
// the SqliteJournalMode / SqliteSynchronous enums are not currently
// re-exported from prax_query::connection (WAL is the default).`;

const multiDatabase = `use prax_query::connection::{MultiDatabaseConfig, DatabaseConfig};

// Configure primary + read replicas
let config = MultiDatabaseConfig::new()
    .primary(DatabaseConfig::from_url("postgres://primary/db")?)
    .replica(DatabaseConfig::from_url("postgres://replica1/db")?)
    .replica(DatabaseConfig::from_url("postgres://replica2/db")?);
// Replicas default to round-robin load balancing. The .load_balance(..)
// setter exists (RoundRobin / Random / First / LeastLatency), but the
// LoadBalanceStrategy enum is not currently re-exported from
// prax_query::connection, so the default is what you get in v0.11.

// Named databases for different purposes
let config = MultiDatabaseConfig::new()
    .primary(DatabaseConfig::from_url("postgres://main/db")?)
    .database("analytics", DatabaseConfig::from_url("postgres://analytics/db")?)
    .database("cache", DatabaseConfig::from_url("postgres://cache/db")?);

// Access specific database
let primary = config.get_primary();
let analytics = config.get("analytics");`;

const envExpansion = `use prax_query::connection::EnvExpander;

// Expand environment variables in connection strings
let expander = EnvExpander::new();

// Supported syntax:
// \${VAR}          - Required variable
// \${VAR:-default} - Variable with default value
// \${VAR:?error}   - Required with custom error message

let url = expander.expand(
    "postgres://\${DB_USER}:\${DB_PASS}@\${DB_HOST:-localhost}:\${DB_PORT:-5432}/\${DB_NAME}"
)?;

// Check for variables before expanding
if EnvExpander::has_variables(&url) {
    let expanded = expander.expand(&url)?;
}`;

const poolConfig = `use prax_query::connection::PoolConfig;
use std::time::Duration;

// Default configuration
let pool = PoolConfig::new()
    .max_connections(20)
    .min_connections(5)
    .connect_timeout(Duration::from_secs(30))
    .acquire_timeout(Duration::from_secs(30))
    .idle_timeout(Duration::from_secs(600))
    .max_lifetime(Duration::from_secs(1800))
    .retry_attempts(3)
    .retry_delay(Duration::from_millis(500))
    .health_check_interval(Duration::from_secs(30));

// Preset configurations
let pool = PoolConfig::low_latency();      // Optimized for fast responses
let pool = PoolConfig::high_throughput();  // Optimized for many connections
let pool = PoolConfig::development();      // Minimal config for dev`;

const sslConfig = `use prax_query::connection::{ConnectionOptions, DatabaseConfig, SslConfig, SslMode};

// SSL modes
SslMode::Disable     // No SSL
SslMode::Allow       // Allow but don't require
SslMode::Prefer      // Prefer SSL (default)
SslMode::Require     // Require SSL
SslMode::VerifyCa    // Verify server certificate
SslMode::VerifyFull  // Verify certificate + hostname

// Simple case: set the mode on the database config builder
let config = DatabaseConfig::postgres()
    .host("secure-db.example.com")
    .ssl_mode(SslMode::VerifyFull)
    .build()?;

// Full SSL configuration (client certs) via ConnectionOptions::ssl
let ssl = SslConfig::new(SslMode::VerifyFull)
    .with_ca_cert("/path/to/ca.crt")
    .with_client_cert("/path/to/client.crt")
    .with_client_key("/path/to/client.key");

let options = ConnectionOptions::new().ssl(ssl);`;
---

<DocsLayout title="Connection & Configuration - 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">Connection & Configuration</h1>
      <p class="text-xl text-muted">
        Prax provides flexible connection string parsing and configuration options for all supported databases.
        Support for environment variables, multi-database setups, and connection pooling.
      </p>
    </header>

    <div class="prose prose-invert max-w-none space-y-8">

    <nav class="mb-8 p-4 bg-surface rounded-lg">
      <h3 class="text-sm font-semibold text-muted mb-2">On this page</h3>
      <ul class="space-y-1 text-sm">
        <li><a href="#parsing" class="text-primary-400 hover:text-primary-300">URL Parsing</a></li>
        <li><a href="#formats" class="text-primary-400 hover:text-primary-300">URL Formats</a></li>
        <li><a href="#builder" class="text-primary-400 hover:text-primary-300">Builder Pattern</a></li>
        <li><a href="#multi-db" class="text-primary-400 hover:text-primary-300">Multi-Database</a></li>
        <li><a href="#env" class="text-primary-400 hover:text-primary-300">Environment Variables</a></li>
        <li><a href="#pool" class="text-primary-400 hover:text-primary-300">Connection Pool</a></li>
        <li><a href="#ssl" class="text-primary-400 hover:text-primary-300">SSL/TLS</a></li>
      </ul>
    </nav>

    <section id="parsing" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">URL Parsing</h2>
      <p class="text-muted mb-4">
        Parse database connection URLs to extract all connection parameters.
      </p>

      <CodeBlock code={parseUrl} lang="rust" filename="Connection String Parsing" />
    </section>

    <section id="formats" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Supported URL Formats</h2>
      <p class="text-muted mb-4">
        Prax supports standard connection URL formats for PostgreSQL, MySQL, and SQLite.
      </p>

      <CodeBlock code={urlFormats} lang="text" filename="URL Formats" />

      <div class="mt-6">
        <h4 class="font-semibold mb-3">Common Query Parameters</h4>
        <table class="w-full text-sm">
          <thead>
            <tr class="border-b border-border">
              <th class="text-left py-2 text-muted">Parameter</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>sslmode</code></td>
              <td>SSL/TLS mode (disable, require, verify-full)</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>connect_timeout</code></td>
              <td>Connection timeout in seconds</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>application_name</code></td>
              <td>Application identifier</td>
            </tr>
            <tr class="border-b border-border/50">
              <td class="py-2"><code>schema</code></td>
              <td>Default schema/search_path</td>
            </tr>
          </tbody>
        </table>
      </div>
    </section>

    <section id="builder" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Builder Pattern</h2>
      <p class="text-muted mb-4">
        Use the builder pattern for programmatic configuration with full type safety.
      </p>

      <CodeBlock code={builderPattern} lang="rust" filename="Configuration Builder" />
    </section>

    <section id="multi-db" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Multi-Database Configuration</h2>
      <p class="text-muted mb-4">
        Configure multiple databases for read replicas, analytics, or different services.
      </p>

      <CodeBlock code={multiDatabase} lang="rust" filename="Multi-Database Setup" />

      <div class="mt-6">
        <h4 class="font-semibold mb-3">Load Balancing Strategies</h4>
        <p class="text-muted text-sm mb-3">
          Available variants (note: <code>LoadBalanceStrategy</code> is not currently re-exported
          from <code>prax_query::connection</code>, so v0.11 configurations use the
          <code>RoundRobin</code> default):
        </p>
        <div class="grid grid-cols-2 gap-4 text-sm">
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">RoundRobin</code>
            <p class="text-muted text-xs mt-1">Distribute evenly across replicas</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">Random</code>
            <p class="text-muted text-xs mt-1">Random replica selection</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">First</code>
            <p class="text-muted text-xs mt-1">Always use first available</p>
          </div>
          <div class="p-3 bg-surface rounded">
            <code class="text-primary-400">LeastLatency</code>
            <p class="text-muted text-xs mt-1">Use lowest latency replica</p>
          </div>
        </div>
      </div>
    </section>

    <section id="env" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Environment Variables</h2>
      <p class="text-muted mb-4">
        Expand environment variables in connection strings for secure configuration.
      </p>

      <CodeBlock code={envExpansion} lang="rust" filename="Environment Variable Expansion" />

      <div class="mt-4 p-4 bg-info-500/10 border border-info-500/30 rounded-lg">
        <h4 class="font-semibold text-info-400 mb-2">💡 Pro Tip</h4>
        <p class="text-sm text-muted">
          Use <code class="text-primary-400">$&#123;VAR:-default&#125;</code> syntax to provide fallback values
          for development environments while keeping production config secure.
        </p>
      </div>
    </section>

    <section id="pool" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">Connection Pool Configuration</h2>
      <p class="text-muted mb-4">
        Configure connection pooling for optimal performance.
      </p>

      <CodeBlock code={poolConfig} lang="rust" filename="Pool Configuration" />

      <div class="mt-4 p-4 bg-warning-500/10 border border-warning-500/30 rounded-lg">
        <p class="text-sm text-muted">
          <strong class="text-warning-400">v0.11:</strong> pool configurations where
          <code>min_connections &gt; max_connections</code> are now a hard error at pool build time
          (previously clamped or ignored).
        </p>
      </div>
    </section>

    <section id="ssl" class="mb-12">
      <h2 class="text-2xl font-semibold mb-4">SSL/TLS Configuration</h2>
      <p class="text-muted mb-4">
        Configure secure connections with SSL/TLS certificates.
      </p>

      <CodeBlock code={sslConfig} lang="rust" filename="SSL Configuration" />

      <div class="mt-4 p-4 bg-surface border border-border rounded-lg">
        <h4 class="font-semibold mb-2">v0.11 TLS Reality (prax-postgres)</h4>
        <ul class="text-sm text-muted list-disc list-inside space-y-1">
          <li><code>sslmode=require</code>, <code>verify-ca</code> and <code>verify-full</code> establish <strong>rustls</strong> connections verified against the <strong>Mozilla root store</strong> (webpki-roots), behind the default <code>tls</code> cargo feature.</li>
          <li>Certificate chain <em>and</em> hostname are always verified — stricter than libpq's <code>sslmode=require</code>, which skips hostname checks.</li>
          <li>Without the <code>tls</code> feature, TLS-requiring modes <strong>fail at pool build time</strong> — never a silent downgrade to plaintext.</li>
        </ul>
      </div>
    </section>
    </div>
  </article>
</DocsLayout>