DEV Community

Varun Krishnan
Varun Krishnan

Posted on

PostgreSQL Connection String: Supabase, Neon, Railway

The short version

A PostgreSQL connection string is a single URL that tells a client how to reach your database. It looks like this:

postgresql://postgres:[YOUR-PASSWORD]@db.abcdefghij.supabase.co:5432/postgres
Enter fullscreen mode Exit fullscreen mode

Every provider builds on the same format, and the differences that trip people up are almost never the syntax. They're the port and host your provider hands you. Supabase alone has three different strings for the same database, and two of them won't work from serverless apps.

Anatomy of a connection string

postgresql://  user  :  password   @     host        :  port  /  database
     |          |         |              |              |          |
  protocol    username  secret         server        port       db name
Enter fullscreen mode Exit fullscreen mode
Part Example Notes
Protocol postgresql:// postgres:// also works in most clients
User postgres In Supabase: postgres.[project-ref] on pooler URLs
Password [your-password] URL-encode special characters (@%40)
Host db.xxxx.supabase.co The server; provider-specific
Port 5432 6543 = transaction pooler (Supabase/Neon)
Database postgres Default database name

You can append query parameters after the database. The two you'll actually meet are ?sslmode=require (force TLS) and ?channel_binding=require.

Supabase: three strings for one database

Supabase is where most people hit the connection-string wall, because the right string depends entirely on where your code runs:

Mode Host Port Best for
Direct db.[project-ref].supabase.co 5432 Migrations, pg_dump, long-lived backend
Shared pooler (session) aws-[region].pooler.supabase.com 5432 Persistent backend on IPv4-only networks
Shared pooler (transaction) aws-[region].pooler.supabase.com 6543 Serverless / edge / short-lived connections
# Direct
postgresql://postgres:[YOUR-PASSWORD]@db.[project-ref].supabase.co:5432/postgres

# Shared pooler session mode
postgres://postgres.[project-ref]:[YOUR-PASSWORD]@aws-[REGION].pooler.supabase.com:5432/postgres

# Shared pooler transaction mode (the one most apps want)
postgres://postgres.[project-ref]:[YOUR-PASSWORD]@aws-[REGION].pooler.supabase.com:6543/postgres
Enter fullscreen mode Exit fullscreen mode

The gotcha: direct connections run on IPv6. On an IPv4-only network you get an ENOTFOUND, not a friendly error, just a silent failure to connect. The shared pooler is IPv4-only and fixes it. And if your app is serverless (Vercel functions, edge runtimes, Lambda), use transaction mode (port 6543). It's built for the many-short-connections pattern serverless forces.

This exact confusion is why tools that accept a connection string usually ask you to grab the transaction pooler URL. dbdiagramr, for instance, has a step in its how-it-works guide telling you to select the Transaction pooler in the Supabase dashboard before pasting. Otherwise the connection silently fails from IPv4-only hosts.

Neon: pooled vs direct

Neon makes the two variants explicit in the hostname:

# Pooled (through PgBouncer, use by default)
postgresql://user:pass@ep-cool-rain-123456-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require

# Direct
postgresql://user:pass@ep-cool-rain-123456.us-east-2.aws.neon.tech/neondb?sslmode=require
Enter fullscreen mode Exit fullscreen mode

Rule of thumb: use the -pooler host unless you have a specific reason not to. It handles thousands of concurrent clients and is the right call for serverless. Grab both from the Connect button in the Neon dashboard.

Railway: the plain standard

Railway gives you a no-frills connection string with no pooler decision to make:

postgresql://postgres:[YOUR-PASSWORD]@[host].railway.app:5432/railway
Enter fullscreen mode Exit fullscreen mode

It's the textbook format, which makes it the easiest one to read and the easiest to misplace a password in. Store it in Railway's Variables tab as DATABASE_URL, never in code.

All the formats in one table

Provider Connection string shape
Standard / Railway postgresql://user:pass@host:5432/db
Supabase (direct) postgresql://postgres:pass@db.[ref].supabase.co:5432/postgres
Supabase (session pooler) postgres://postgres.[ref]:pass@aws-[region].pooler.supabase.com:5432/postgres
Supabase (transaction pooler) postgres://postgres.[ref]:pass@aws-[region].pooler.supabase.com:6543/postgres
Neon (pooled) postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/db?sslmode=require
Neon (direct) postgresql://user:pass@ep-xxx.region.aws.neon.tech/db?sslmode=require

Gotchas that will actually bite you

  • IPv4 vs IPv6. Supabase direct connections are IPv6. If you're on an IPv4-only network or using a tool on an IPv4-only host, you'll get ENOTFOUND and no obvious reason why. The pooler strings are IPv4 and sidestep it.
  • Serverless wants the transaction pooler. Long-lived backends can hold a connection. Serverless functions can't. Each invocation opens a new one. Session-pooling chokes on that pattern; transaction pooling (port 6543) exists for exactly it.
  • A connection string is a secret. It contains your password in plain text. Never commit it, never paste it into a shared doc, and use a pooler/proxy string when a third-party tool needs it. Most good tools never store it beyond the one request. (dbdiagramr introspects your schema and discards the string immediately.)
  • URL-encode passwords. A password containing @, :, or / breaks the URL. Encode those characters (@%40) before pasting.

Curious what that pooler URL actually contains after it connects? The Supabase auth schema diagram was created by introspecting a live Supabase database through a transaction pooler string. Users, identities, sessions, and refresh_tokens rendered as relationships.

FAQ

What is a PostgreSQL connection string?
It's a single URL containing everything needed to reach a Postgres database: protocol, username, password, host, port, and database name (e.g. postgresql://user:pass@host:5432/db).

Should I use a direct connection or a pooler?
For a persistent backend that can hold a connection open, direct is fine. For serverless or edge functions that open a connection per invocation, use a pooler, either Supabase transaction mode (port 6543) or a Neon -pooler host.

Why does my Supabase connection fail with ENOTFOUND?
You're probably using the direct connection string over IPv4, and Supabase's direct endpoint is IPv6. Switch to a shared pooler string (aws-[region].pooler.supabase.com) or add the IPv4 add-on.

Is my connection string a secret?
Yes, it's your password in plain text. Keep it in environment variables, rotate it if it leaks, and only hand it to tools that won't store it.

Try It

Live: https://dbdiagramr.space

GitHub: https://github.com/VarunKvK/dbdiagramr

If this is useful to you, a GitHub star helps a solo dev keep building in public. I hit the IPv6 wall the first time I tried to visualize a Supabase database from a serverless app. Figured others would save the hour.

Top comments (1)

Collapse
 
freerave profile image
freerave

Nice breakdown — especially the distinction between direct, session, and transaction pooling.

One thing I’m curious about: when you recommend transaction pooling for serverless, how do you think about session semantics, not just connection count?

Since successive transactions aren't guaranteed to land on the same PostgreSQL backend, things like session-level SETs, temp tables, advisory locks, LISTEN/NOTIFY, or some prepared-statement strategies can behave differently or break entirely.

Do you treat transaction pooling as the default only when the application is deliberately session-stateless, or do you rely on the ORM/driver to abstract those differences away?