Prisma Setup
Configure Prisma with pooled runtime traffic and a direct migration URL. Covers schema configuration, migration commands, shadow database permissions, connection pooling, and production deployment patterns.
Prisma is the recommended ORM for AxiomDB. This guide covers every aspect of integrating Prisma with AxiomDB's dual-URL connection model, from initial configuration through production deployment patterns.
The dual-URL model
AxiomDB provides two URLs per branch. Prisma uses them for different purposes:
| URL | Port | Role | Prisma usage |
|---|---|---|---|
DATABASE_URL | 6432 | Runtime read/write (pooled) | Application queries, findFirst, create, update, delete |
DIRECT_URL | 5432 | Owner/migration (direct) | migrate dev, migrate deploy, db push, db pull, shadow database |
Configuration
Environment variables
Use both URLs in .env:
DATABASE_URL="postgresql://square_experience_rw:***@db.squareexp.com:6432/sq_square_experience_main?sslmode=require"
DIRECT_URL="postgresql://square_experience_owner:***@db.squareexp.com:5432/sq_square_experience_main?sslmode=require"Schema configuration
Wire both in schema.prisma:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}Why two URLs are required
Prisma Migrate needs direct Postgres access for operations that PgBouncer cannot safely proxy:
- Shadow database creation — Prisma creates a temporary database to diff your schema against the current state. The runtime role (
_rw) intentionally cannot create databases — this is a security boundary. - Advisory locks — Migrations use advisory locks to prevent concurrent schema changes. PgBouncer in transaction mode doesn't support advisory locks.
- Prepared statements — Some migration operations use prepared statements that PgBouncer may not handle correctly.
- DDL operations —
CREATE TABLE,ALTER TABLE,CREATE INDEX, and other DDL operations require the owner role.
The runtime role only has CONNECT and read/write privileges. It cannot create databases, manage roles, or perform DDL operations. If your application's database credentials are compromised, the attacker cannot modify the schema.
Migration commands
Development
# Create a new migration from schema changes
npx prisma migrate dev --name <migration-name>
# Reset the database (drops all data, reapplies migrations)
npx prisma migrate reset
# Check migration status
npx prisma migrate statusmigrate dev uses DIRECT_URL to:
- Create a shadow database (temporary, using the owner role).
- Diff your Prisma schema against the shadow database.
- Generate SQL migration files.
- Apply the migration to the development database.
- Record the migration in
_prisma_migrations.
Production
# Apply pending migrations (no shadow database, no prompts)
npx prisma migrate deploymigrate deploy uses DIRECT_URL to:
- Read pending migrations from the
migrations/directory. - Apply them in order.
- Record each migration in
_prisma_migrations.
migrate deploy does not create a shadow database, does not generate new migrations, and does not prompt for confirmation. It is designed for CI/CD pipelines and production deployments.
Schema inspection
# Pull the current database schema into Prisma
npx prisma db pull
# Generate the Prisma client
npx prisma generatedb pull uses DIRECT_URL to introspect the database schema and update your schema.prisma file. This is useful when the database schema was modified outside of Prisma (e.g., through the SQL editor or manual migrations).
Shadow database permissions
The direct owner role (_owner) is allowed to create the temporary shadow database Prisma needs. Runtime roles are intentionally not allowed to create databases.
How the shadow database works
When you run migrate dev, Prisma:
- Creates a temporary database (e.g.,
_prisma_shadow_db). - Applies all existing migrations to the shadow database.
- Applies your schema changes to the shadow database.
- Diffs the shadow database against the current database.
- Generates a migration SQL file.
- Drops the shadow database.
- Applies the migration to the real database.
This process requires the CREATEDB privilege, which only the owner role has.
Troubleshooting shadow database errors
If Prisma says it cannot create a shadow database:
- Verify
directUrlis set inschema.prisma. - Verify
DIRECT_URLpoints at port5432(direct Postgres, not PgBouncer). - Verify your device IP is allowlisted for direct access.
- Verify the owner role has
CREATEDBprivilege.
-- Check role privileges
SELECT rolname, rolcreatedb FROM pg_roles WHERE rolname LIKE '%_owner';PgBouncer compatibility
AxiomDB runs PgBouncer on port 6432 in session mode for Prisma compatibility.
Why session mode
PgBouncer supports three pooling modes:
| Mode | Prisma compatible | Description |
|---|---|---|
session | Yes | A server connection is assigned to the client for the entire session. |
transaction | No | A server connection is assigned only during a transaction. |
statement | No | A server connection is assigned only for a single statement. |
Prisma requires session mode because it:
- Uses prepared statements for query optimization.
- Holds connections open for the lifetime of the application process.
- Uses advisory locks during migrations (which require session affinity).
Connection pooling best practices
Configure Prisma's connection pool to work within PgBouncer's limits:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
// Connection pool configuration
relationMode = "prisma" // Optional: use Prisma's relation mode
}In your application code, configure the Prisma client with appropriate pool settings:
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
});Connection pool sizing
The default Prisma connection pool size is num_cpus * 2 + 1. For most applications:
| Environment | Recommended pool size |
|---|---|
| Development | 2-5 |
| Staging | 5-10 |
| Production | 10-20 |
PgBouncer's default_pool_size controls how many Postgres connections each user/database pair can use. Ensure your Prisma pool size doesn't exceed PgBouncer's limits.
Framework snippets
The AxiomDB console and CLI provide pre-formatted connection blocks for every major framework. Click Connect on any branch to see snippets for:
- Prisma —
schema.prismadatasource block +.envvariables. - Drizzle —
drizzle.config.tswith connection string. - Kysely — TypeScript pool configuration.
- node-postgres —
pg.Poolconfiguration. - SQLAlchemy — Python connection string.
- Django —
DATABASESsettings. - Laravel —
.envdatabase configuration. - Go pgx —
pgxpoolconfiguration. - Rust SQLx —
Pool<Postgres>configuration.
Production deployment
CI/CD pipeline
# Example GitHub Actions workflow
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run migrations
run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DIRECT_URL: ${{ secrets.DIRECT_URL }}
- name: Generate Prisma client
run: npx prisma generate
- name: Deploy application
run: npm run deployEnvironment-specific URLs
Use different branches for different environments:
main branch → production
staging branch → staging environment
dev branch → development environment
feature-* → preview environmentsEach environment gets its own .env with branch-specific URLs:
# Production
DATABASE_URL="postgresql://...@db.squareexp.com:6432/sq_app_main?sslmode=require"
DIRECT_URL="postgresql://...@db.squareexp.com:5432/sq_app_main?sslmode=require"
# Staging
DATABASE_URL="postgresql://...@db.squareexp.com:6432/sq_app_main_br_staging?sslmode=require"
DIRECT_URL="postgresql://...@db.squareexp.com:5432/sq_app_main_br_staging?sslmode=require"Migration rollback strategy
AxiomDB's branch model makes migration rollbacks safer:
- Before migration — Create a snapshot of the target branch.
- Run migration — Apply the migration to a feature branch first.
- Test — Verify schema changes and data integrity on the feature branch.
- Deploy to main — Apply the same migration to the main branch.
- Rollback if needed — Restore from the snapshot into a new branch.
# Create snapshot before risky migration
axm backups create --name pre-migration-2026-05-08
# Create feature branch for testing
axm branches create --name test-migration --source main --lifespan 7d
# Get branch URLs
axm branches urls --name test-migration
# Run migration against feature branch
npx prisma migrate dev --name add-new-table
# Test thoroughly...
# Apply to main when ready
axm branches urls --name main
npx prisma migrate deployTroubleshooting
"Can't create database" error
Cause: The runtime role is being used for migrations instead of the owner role.
Fix: Verify DIRECT_URL is set and points at port 5432. Prisma uses directUrl for shadow database creation.
"prepared statement does not exist" error
Cause: Prisma is trying to use prepared statements through PgBouncer in transaction mode.
Fix: AxiomDB runs PgBouncer in session mode by default. If you're using a custom PgBouncer configuration, switch to session mode:
[pgbouncer]
pool_mode = session"connection refused" on migration
Cause: Your device IP is not allowlisted for direct access (port 5432).
Fix: Add your IP to the network allowlist:
axm network allow --currentMigrations are slow
Cause: Large tables with many rows can make migrations slow.
Fix: Consider:
- Using
migrate deployinstead ofmigrate devin production (no shadow database). - Running migrations during low-traffic periods.
- Using Prisma's
relationMode = "prisma"for better control over foreign key constraints.
_prisma_migrations table not found
Cause: The branch database was created without running any Prisma migrations.
Fix: Run npx prisma migrate dev --name init to create the migrations table and apply initial migrations. The monitoring endpoints return not_configured when the table doesn't exist — this is expected behavior, not an error.
Do not migrate through PgBouncer
If Prisma says it cannot create a shadow database or cannot use prepared statements, confirm directUrl is set and points at port 5432. Migrations through the pooled endpoint (port 6432) will fail.