Skip to content
This project is part of pgEdge Labs and is under active development. APIs and features may change without notice.

ORM and framework integration

Every framework on this page connects the same way. The CLI prints a standard libpq connection URI, and each framework below has one place that accepts it. The string carries nothing specific to pgEdge, so these frameworks take it without changes. A driver that expects its own URI scheme, such as JDBC's jdbc:postgresql:// prefix, needs the parts rather than the assembled URI.

The connect an application guides cover the command itself, its flags, and how to handle the live password the string carries, one for Managed and one for BYOC. This page starts from the string.

Get the string

On managed, pgedge env pull writes the URI into .env as DATABASE_URL, from a project folder linked to the database. The Linking a Project Folder to a pgEdge Starfleet Managed Database guide covers linking a folder. The rest of this section gets the string by hand, which BYOC and scripts need.

On managed, one command prints the URI for a database:

pgedge starfleet managed database connection-string <db-id>

On byoc the same command names a node when the database has several, because a BYOC database carries one connection block per node:

pgedge starfleet byoc database connection-string <db-id> --node n1

Either prints one line and nothing else:

postgresql://app:<password>@<host>:<port>/<database>?sslmode=require

Most of the recipes below read that line from an environment file. Write the file under a restrictive umask and stop on a failed read, so a mistyped identifier surfaces here rather than when the application starts:

umask 077
if ! pgedge starfleet managed database connection-string <db-id> \
    > uri.txt; then
    echo "connection-string failed; .env was not written" >&2
    exit 1
fi
printf "DATABASE_URL='%s'\n" "$(cat uri.txt)" > .env

Single quotes, not double: a password may carry $, which the URI leaves unescaped, and a double-quoted value is expanded by a shell that sources the file. A single quote itself never appears raw in the URI, so the single-quoted form is safe.

Remove the temporary file once the env file holds the string:

rm -f uri.txt

Keep the query string on the end of the URI. The CLI always appends sslmode=require, and a URI trimmed back to its host and database drops the setting without saying so.

psql

psql reads the same URI, which makes it the shortest way to prove the string before a framework is in the picture. Passing the URI as an argument puts the password in the process list, so take the env format and let psql read the PG* variables:

umask 077
if ! pgedge starfleet managed database connection-string <db-id> \
    --format env > pg.env; then
    echo "connection-string failed; pg.env holds nothing" >&2
    exit 1
fi
. ./pg.env
export PGHOST PGPORT PGDATABASE PGUSER PGPASSWORD PGSSLMODE
psql -c 'select version()'
rm -f pg.env

A row back from that says the host resolves, the TLS handshake completes and the role authenticates. A framework that fails after this one succeeded is failing on its own configuration rather than on the database.

Prisma

Prisma takes the URL from the datasource block in schema.prisma, and the generated block already points at an environment variable:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Set DATABASE_URL to the string the CLI printed. Prisma's own default is sslmode=prefer, which accepts a plain-text connection when TLS is not available, so the sslmode=require the CLI appends is what holds the connection encrypted. The Prisma Postgres connector reference lists the other arguments Prisma reads from the query string.

Drizzle

Drizzle connects through the pg driver, which parses the URI itself, so the string goes straight into the constructor:

import { drizzle } from 'drizzle-orm/node-postgres';

const db = drizzle(process.env.DATABASE_URL);

Drizzle Kit holds its own copy for migrations, under dbCredentials in drizzle.config.ts:

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'postgresql',
  dbCredentials: { url: process.env.DATABASE_URL },
});

Both read the same variable, so one env file covers the application and the migration tool. The Drizzle Postgres guide covers the driver alternatives.

SQLAlchemy and Alembic

SQLAlchemy builds an engine from the URI directly:

import os
from sqlalchemy import create_engine

engine = create_engine(os.environ["DATABASE_URL"])

Alembic reads the URL from the sqlalchemy.url key of alembic.ini, a file most projects commit, and a live password does not belong in a committed file. Set the value at run time from env.py instead:

import os
from alembic import context

context.config.set_main_option(
    "sqlalchemy.url", os.environ["DATABASE_URL"])

The Alembic tutorial describes the rest of that file.

Django

Django reads discrete parameters from the DATABASES setting rather than a URL, so the string has to be split or parsed. The split version reads the same values the env format prints:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ["PGDATABASE"],
        "USER": os.environ["PGUSER"],
        "PASSWORD": os.environ["PGPASSWORD"],
        "HOST": os.environ["PGHOST"],
        "PORT": os.environ["PGPORT"],
        "OPTIONS": {"sslmode": "require"},
    }
}

Django's Postgres backend passes OPTIONS to the driver's connection constructor, which is why the TLS setting sits there rather than beside the host. The alternative is dj-database-url, which parses a URI into the same dictionary and reads DATABASE_URL by default:

import dj_database_url

DATABASES = {"default": dj_database_url.config()}

The Django databases reference covers what else the backend accepts.

Ruby on Rails

Active Record reads DATABASE_URL from the environment with no configuration at all, so that variable and an empty config/database.yml are enough to connect. A url key in the YAML takes precedence over the variable, and reading the variable through ERB pins one environment to one connection without committing the string:

production:
  url: <%= ENV['DATABASE_URL'] %>

The Rails configuration guide describes how the two sources are merged.

Go (pgx)

pgx parses the URI itself, so the pool constructor takes the string with nothing in between:

pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
if err != nil {
    return err
}
defer pool.Close()

pgx reads sslmode out of the query string the way libpq does, so the string needs no Go-side TLS setup to connect over TLS. The pgxpool documentation covers the pool options worth setting beyond the URL.

Next steps

  • The connect an application guides for Managed and BYOC describe the connection-string command, its flags and how to handle the password the output carries.
  • The load schema and data guide describes pulling the connection fields out individually and turning them into .pgpass entries.
  • The rotate a managed database password guide describes what happens to a string an application already holds once the role behind the string is rotated.
  • The CI and automation guide describes running the CLI from a pipeline, including where the credentials it needs should live.