Connections
Learn how to connect to your AxiomDB databases using our dual-URL system, PgBouncer pooling, TLS, and popular coding frameworks.
Connections
Whenever you create a new database branch in AxiomDB, it gives you two separate connection URLs. This dual-URL setup is designed to give you the best of both worlds: one URL is optimized for running your everyday application traffic, and the other is a direct line for administrative tasks like running schema migrations.
Before we dive in, note that every connection is strictly secured. You must connect using TLS (sslmode=require)—we don't allow unencrypted plaintext traffic here.
The Dual-URL Model
Think of this dual-URL system as two different entryways into your database branch:
⚡ How the Dual-URL Connection Works ⚡
========================================================================
[DATABASE_URL (Port 6432)] ──► PgBouncer (Session Mode) ──► Postgres 16 (Branch DB)
└─► Best for: General application runtime traffic that scales.
[DIRECT_URL (Port 5432)] ──► Direct Connection ─────────► Postgres 16 (Branch DB)
└─► Best for: Running migrations, CLI administration, and pg_dump.
🔒 Security Guard: TLS (sslmode=require) is strictly required on both!
========================================================================When to Use Each URL
Here is a quick guide on when to use which URL:
- Application queries: Use
DATABASE_URL. This routes traffic through PgBouncer's connection pool, which keeps your application fast and prevents you from running out of database connections. - Prisma migrations / db push: Use
DIRECT_URL. Migration tools need a direct link to make changes to your tables and manage locks. - Backups & Restores (pg_dump / pg_restore): Use
DIRECT_URL. These tools need session-level control and direct database access to function properly. - Interactive psql sessions: Use
DIRECT_URL. If you're manually running commands, connecting directly gives you the most control. - High-throughput batch jobs: Use
DATABASE_URL. Connection pooling will handle bursty traffic patterns without breaking a sweat.
URL Anatomy
Let's look at how these connection strings are structured.
DATABASE_URL (Runtime)
Here is a breakdown of what makes up the pooled connection URL:
⚡ DATABASE_URL Anatomy ⚡
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[postgresql://] ──► The Protocol (Postgres default)
[role] ──► Your Username/Access Identity
[:] ──► The Separator
[password] ──► URL-encoded Secret Key
[@] ──► The Gateway Anchor
[host] ──► gateway.axiomdb.io
[:] ──► The Port Separator
[6432] ──► PgBouncer connection pooling port
[/] ──► Database slash
[database] ──► Target DB (sq_project_branch)
[?] ──► Query parameters
[sslmode=req] ──► TLS Enforcement
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━For example:
postgresql://payments_prod_rw:s3cure%21p%40ss@gateway.axiomdb.io:6432/sq_payments_prod_br_fix-checkout-bug?sslmode=requireDIRECT_URL (Direct)
The direct connection URL looks almost identical, but it routes through port 5432 instead of 6432:
⚡ DIRECT_URL Anatomy ⚡
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[postgresql://] ──► The Protocol (Postgres default)
[role] ──► Your Username/Access Identity
[:] ──► The Separator
[password] ──► URL-encoded Secret Key
[@] ──► The Gateway Anchor
[host] ──► gateway.axiomdb.io
[:] ──► The Port Separator
[5432] ──► Direct Postgres database port
[/] ──► Database slash
[database] ──► Target DB (sq_project_branch)
[?] ──► Query parameters
[sslmode=req] ──► TLS Enforcement
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━For example:
postgresql://payments_prod_owner:s3cure%21p%40ss@gateway.axiomdb.io:5432/sq_payments_prod_br_fix-checkout-bug?sslmode=requireURL Decoding
Since passwords can contain special symbols, they need to be URL-encoded in the connection string. Here is how they translate:
| Component | Encoded Character | Decoded Value |
|---|---|---|
| Password | s3cure%21p%40ss | s3cure!p@ss |
%21 | — | ! |
%40 | — | @ |
%2F | — | / |
%25 | — | % |
How passwords are made
We generate passwords using 32 bytes of secure random data. They are base64url-encoded, which means they don't contain any weird symbols that mess up connection strings, unless you provide a custom password yourself.
Environment Variable Naming Conventions
To keep things neat, we map these connection strings to common environment variables.
Main Branch
DATABASE_URL:postgresql://...@gateway.axiomdb.io:6432/sq_<key>_<env>?sslmode=require(Pooled application connection)DIRECT_URL:postgresql://...@gateway.axiomdb.io:5432/sq_<key>_<env>?sslmode=require(Direct admin connection)
Child Branch
DATABASE_URL:postgresql://...@gateway.axiomdb.io:6432/sq_<key>_<env>_br_<slug>?sslmode=require(Pooled application connection)DIRECT_URL:postgresql://...@gateway.axiomdb.io:5432/sq_<key>_<env>_br_<slug>?sslmode=require(Direct admin connection)
Internal Routing Keys
Behind the scenes, we track your connections using a simple key format:
project:<project_id>:branch:<branch_id>:role:<role_name>For example: project:proj_a1b2c3d4:branch:br_x1y2z3:role:payments_prod_rw. You'll see these show up in logs and metrics.
SSL/TLS Requirements
Security isn't optional on AxiomDB. We enforce TLS on every single connection, and our gateway will immediately block any attempts to connect over plaintext.
TLS Details
- sslmode: Requires
requireat minimum, but we recommendverify-fullfor maximum safety. - TLS version: Requires TLS 1.2 or higher. We strongly prefer TLS 1.3.
- Certificates: Our gateway uses certificates signed by public CAs, so you don't need to load any custom CA bundles.
- Client certificates: You don't need them by default, but we support them if you want an extra layer of security.
sslmode Options
| Mode | What it does | Supported? |
|---|---|---|
disable | Plain text connection, no security | ❌ Blocked |
allow | Tries TLS, drops back to plaintext | ❌ Blocked |
prefer | Tries plaintext, upgrades to TLS if available | ❌ Blocked |
require | Encrypted connection, does not verify certs | ✅ Supported |
verify-ca | Encrypted connection, verifies CA certificate | ✅ Supported |
verify-full | Encrypted connection, verifies CA and checks hostname | ✅ Recommended |
Example connection strings:
# Basic TLS connection
postgresql://user:pass@gateway.axiomdb.io:6432/db?sslmode=require
# Recommended production connection
postgresql://user:pass@gateway.axiomdb.io:6432/db?sslmode=verify-fullConnection Pooling (PgBouncer)
When you use the pooled DATABASE_URL (port 6432), you are routing your traffic through PgBouncer running in session mode.
Why Session Mode?
We configure PgBouncer in session mode for a few simple reasons:
- Prepared Statements: Tools like Prisma rely on prepared statements. Transaction-mode pooling breaks them, but session mode keeps them working.
- Session Settings: Commands like
SET search_pathor setting time zones work properly. - Temporary Data: Temporary tables and database advisory locks stay active for as long as your connection is open.
PgBouncer Specs
- Pool mode:
session(dedicated connection for the lifetime of your client session). - Default pool size:
20max database connections per user/database pair. - Max client connections: Up to
200total clients can connect at once. - Server idle timeout: Server connections close after
300seconds of inactivity. - Client idle timeout: Client connections drop after
600seconds of inactivity.
PgBouncer vs Direct Connection
⚡ Connection Path Comparison ⚡
========================================================================
[Client App]
│
├─► (Port 6432) ──► [PgBouncer (Session Mode)] ──► [Postgres (Branch DB)]
│ └─► Up to 200 concurrent clients, max 20 DB connections.
│
└─► (Port 5432) ─────────────────────────────────► [Postgres (Branch DB)]
└─► Direct connection bypasses pooling.
========================================================================How to Prevent Connection Exhaustion
If all 20 database connections are active, new requests will wait in PgBouncer's queue. If that queue fills past 200, you'll get an error: ERROR: pgbouncer cannot connect to server.
To avoid this, follow these practices:
- Configure a connection limit in your application (like Prisma's
connection_limit). - Keep your application's connection limit low (typically 10 or less per instance).
- Never use the
DIRECT_URLfor normal application queries—reserve it for migrations.
Framework Integration Snippets
Here is how you configure various popular frameworks to use the dual-URL structure.
Prisma
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
generator client {
provider = "prisma-client-js"
}# .env
DATABASE_URL="postgresql://payments_prod_rw:password@gateway.axiomdb.io:6432/sq_payments_prod?sslmode=require"
DIRECT_URL="postgresql://payments_prod_owner:password@gateway.axiomdb.io:5432/sq_payments_prod?sslmode=require"# Run migrations using the direct URL
npx prisma migrate deploy
# Normal application queries will use the pooled DATABASE_URL automatically
npx prisma generateDrizzle
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DIRECT_URL!,
},
});// src/db/index.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
ssl: { rejectUnauthorized: true },
});
export const db = drizzle(pool);Kysely
// src/db.ts
import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
interface Database {
users: UsersTable;
orders: OrdersTable;
}
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
ssl: { rejectUnauthorized: true },
});
export const db = new Kysely<Database>({
dialect: new PostgresDialect({ pool }),
});node-postgres (pg)
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
ssl: { rejectUnauthorized: true },
});
const { rows } = await pool.query("SELECT NOW()");SQLAlchemy (Python)
# config.py
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
DATABASE_URL = os.environ["DATABASE_URL"]
engine = create_engine(
DATABASE_URL,
pool_size=10,
max_overflow=0,
pool_pre_ping=True,
connect_args={"sslmode": "require"},
)
SessionLocal = sessionmaker(bind=engine)Django
# settings.py
import os
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "sq_payments_prod",
"USER": "payments_prod_rw",
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": "gateway.axiomdb.io",
"PORT": "6432",
"OPTIONS": {
"sslmode": "require",
},
"CONN_MAX_AGE": 600,
}
}Laravel
// config/database.php
'connections' => [
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => 'gateway.axiomdb.io',
'port' => '6432',
'database' => 'sq_payments_prod',
'username' => 'payments_prod_rw',
'password' => env('DB_PASSWORD'),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'require',
],
],Go (pgx)
// db.go
package db
import (
"context"
"os"
"github.com/jackc/pgx/v5/pgxpool"
)
func NewPool(ctx context.Context) (*pgxpool.Pool, error) {
url := os.Getenv("DATABASE_URL")
config, err := pgxpool.ParseConfig(url)
if err != nil {
return nil, err
}
config.MaxConns = 10
config.MinConns = 2
return pgxpool.NewWithConfig(ctx, config)
}Rust (SQLx)
// src/db.rs
use sqlx::postgres::PgPoolOptions;
use std::env;
pub async fn create_pool() -> sqlx::PgPool {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgPoolOptions::new()
.max_connections(10)
.acquire_timeout(std::time::Duration::from_secs(5))
.connect(&database_url)
.await
.expect("Failed to create pool")
}Gateway Architecture
The AxiomDB gateway is an Axum-based Rust service running on port 4060. It handles:
⚡ Gateway Layer Schematic ⚡
========================================================================
[Client Requests]
│
▼
┌─────────────────────────────────────────────────────────────┐
│ AxiomDB Gateway (:4060) │
│ ● Authentication: Verifies PASETO v4 signatures. │
│ ● Rate Limiting: Limits requests to 1000/min per project. │
│ ● Connection Routing: Routes connection requests: │
│ - Port 6432 ──► PgBouncer │
│ - Port 5432 ──► Direct Postgres │
└─────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Target PostgreSQL Cluster │
│ (Your Branch Databases) │
└─────────────────────────────────────────────────────────────┘
========================================================================PASETO v4 Authentication
All database connections and API requests are secure by default, authenticated using PASETO v4 tokens signed with Ed25519 keys.
These tokens store metadata like:
- Who is connecting (
subrole) - The project and branch IDs
- The specific target database name
- Issuance and expiration timestamps
The gateway checks this token before routing your connection anywhere, keeping your database secure.
Connection Troubleshooting
Common Errors & How to Fix Them
- SSL connection is required: You forgot to add sslmode parameters. Make sure your connection string has
?sslmode=requireat the end. - pgbouncer cannot connect to server: The connection pool is full. Try lowering your app's
connection_limitor useDIRECT_URLfor migration scripts. - database "X" does not exist: Double check the database name. You can find the exact name under your branch details.
- role "X" does not exist: Check that you are connecting using the correct username.
- connection timed out: Your IP address probably isn't in the project's network allowlist. Go to the project settings to add your IP, or toggle the network rules.
- password authentication failed: You might have an outdated password. You can rotate and regenerate credentials via
POST /v1/branches/:bid/rotate.
Testing Connectivity
You can test connections using simple CLI commands:
# Test the pooled connection
psql "postgresql://payments_prod_rw:password@gateway.axiomdb.io:6432/sq_payments_prod?sslmode=require" -c "SELECT 1;"
# Test the direct connection
psql "postgresql://payments_prod_owner:password@gateway.axiomdb.io:5432/sq_payments_prod?sslmode=require" -c "SELECT 1;"
# Confirm that TLS is working
psql "postgresql://payments_prod_rw:password@gateway.axiomdb.io:6432/sq_payments_prod?sslmode=require" -c "SHOW ssl;"How is this guide?
