StackPractices
intermediate By Mathias Paulenko

Connect to PostgreSQL

How to connect to PostgreSQL databases in Python, JavaScript, and Java.

Topics: databases

Overview

PostgreSQL runs a huge share of production workloads, but actually connecting to it trips people up in three places: picking a driver, getting SSL right, and pooling connections so max_connections doesn’t run out. Here’s the working setup in Python, JavaScript, and Java — copy-paste ready.

When to Use

Use this resource when:

  • Building web applications that persist data to PostgreSQL
  • Migrating from SQLite or MySQL to PostgreSQL
  • Setting up data pipelines that read from or write to PostgreSQL

If you need MySQL instead, see Connect to MySQL.

Solution

Python

import psycopg2
from psycopg2.extras import RealDictCursor

# psycopg2 also reads PGHOST, PGDATABASE, PGUSER, PGPASSWORD from the environment
conn = psycopg2.connect(
    host="localhost",
    database="mydb",
    user="user",
    password="pass",
    sslmode="prefer"  # use "verify-full" with sslrootcert in production
)

cursor = conn.cursor(cursor_factory=RealDictCursor)
cursor.execute("SELECT * FROM users WHERE id = %s", (1,))
row = cursor.fetchone()
cursor.close()
conn.close()

JavaScript

const { Pool } = require('pg');

const pool = new Pool({
    host: process.env.PGHOST || 'localhost',
    database: process.env.PGDATABASE || 'mydb',
    user: process.env.PGUSER || 'user',
    password: process.env.PGPASSWORD,
    // Local dev needs no SSL; in production use ssl: { rejectUnauthorized: true }
    // and point PGSSLROOTCERT at your CA cert. Never disable verification.
    ssl: false,
    max: 20
});

async function getUser(id) {
    const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
    return result.rows[0];
}

// On shutdown, drain the pool: await pool.end();

Java

import java.sql.*;

public class PostgresConnect {
    public Connection connect() throws SQLException {
        // Production: append ?sslmode=verify-full&sslrootcert=/path/to/ca.crt
        String url = "jdbc:postgresql://localhost:5432/mydb";
        return DriverManager.getConnection(url, "user", "pass");
    }

    public void queryUser(int id) throws SQLException {
        try (Connection conn = connect();
             PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
            stmt.setInt(1, id);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                System.out.println(rs.getString("email"));
            }
        }
    }
}

Explanation

All three examples pass user input through prepared statements (%s, $1, ?) instead of string interpolation, so the driver escapes values for you and SQL injection is off the table. On the Python side we go with psycopg2, which is still the adapter you’ll run into most often. The Node version goes through pg’s Pool, so requests share a fixed set of connections rather than opening a new TCP session per query. And the Java version uses plain JDBC wrapped in try-with-resources, which closes the connection even when a query throws.

flowchart diagram: App[Application

Once connected, you can execute raw SQL queries or use an ORM for CRUD operations.

Variants

TechnologyApproachNotes
PythonasyncpgAsync PostgreSQL driver for asyncio
JavaScriptpg-promiseHelper library with transactions and tasks
JavaHikariCPHigh-performance JDBC connection pool

What Works

  1. Use a connection pool in production — a fresh connection per request will exhaust max_connections fast
  2. Keep credentials in environment variables or a secrets manager, never in source
  3. Turn on SSL for production (sslmode=verify-full once you’ve got the CA cert)
  4. Always parameterize; values concatenated into SQL strings are how injections happen
  5. Close cursors and connections, or let context managers / try-with-resources do it

Common Mistakes

  1. Hardcoding database credentials in source code
  2. Opening a new connection per query instead of pooling
  3. Leaking connections until Postgres hits “too many connections” errors
  4. Turning off certificate verification in production (rejectUnauthorized: false, sslmode=disable)
  5. Building queries with f-strings or template literals

Advanced Examples

Python with context manager and connection pool

import psycopg2
from psycopg2 import pool
from contextlib import contextmanager

# Create a connection pool
pg_pool = pool.SimpleConnectionPool(
    minconn=1,
    maxconn=10,
    host="localhost",
    database="mydb",
    user="user",
    password="pass",
    sslmode="prefer"
)

@contextmanager
def get_db_cursor():
    conn = pg_pool.getconn()
    try:
        cursor = conn.cursor()
        yield cursor
        conn.commit()
        cursor.close()
    except Exception:
        conn.rollback()
        raise
    finally:
        pg_pool.putconn(conn)

# Usage
with get_db_cursor() as cur:
    cur.execute("SELECT * FROM users WHERE active = %s", (True,))
    rows = cur.fetchall()
    for row in rows:
        print(row)

Python async with asyncpg

import asyncio
import asyncpg

async def main():
    conn = await asyncpg.connect(
        host="localhost",
        database="mydb",
        user="user",
        password="pass",
        ssl="prefer"
    )

    # Parameterized query
    row = await conn.fetchrow(
        "SELECT * FROM users WHERE id = $1", 1
    )
    print(row)

    # Batch insert
    await conn.executemany(
        "INSERT INTO logs (level, message) VALUES ($1, $2)",
        [("INFO", "startup"), ("WARN", "high latency")]
    )

    await conn.close()

asyncio.run(main())

JavaScript with transaction handling

const { Pool } = require('pg');

const pool = new Pool({
    host: process.env.PGHOST || 'localhost',
    database: process.env.PGDATABASE || 'mydb',
    user: process.env.PGUSER || 'user',
    password: process.env.PGPASSWORD,
    ssl: false, // same rule as above: verify certs in production
    max: 20,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 5000
});

async function transferBalance(fromId, toId, amount) {
    const client = await pool.connect();
    try {
        await client.query('BEGIN');

        await client.query(
            'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
            [amount, fromId]
        );

        await client.query(
            'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
            [amount, toId]
        );

        await client.query('COMMIT');
    } catch (err) {
        await client.query('ROLLBACK');
        throw err;
    } finally {
        client.release();
    }
}

Java with HikariCP connection pool

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.*;

public class PostgresPool {
    private static final HikariDataSource ds;

    static {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
        config.setUsername("user");
        config.setPassword("pass");
        config.addDataSourceProperty("sslmode", "prefer");
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setIdleTimeout(30000);
        config.setConnectionTimeout(5000);
        ds = new HikariDataSource(config);
    }

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    // Call ds.close() when the application shuts down
    public static void batchInsert(List<String> emails) throws SQLException {
        try (Connection conn = getConnection();
             PreparedStatement stmt = conn.prepareStatement(
                 "INSERT INTO users (email) VALUES (?)")) {
            for (String email : emails) {
                stmt.setString(1, email);
                stmt.addBatch();
            }
            stmt.executeBatch();
        }
    }
}

Additional Variants

TechnologyDriverAsyncPoolingNotes
Pythonpsycopg2NoManual or SimpleConnectionPoolMature, stable
Pythonpsycopg3YesBuilt-inRecommended for new projects
PythonasyncpgYesBuilt-inFastest async driver
JavaScriptpgYes (Promise)Pool built-inStandard Node.js driver
JavaScriptpg-promiseYesBuilt-inExtra helpers for tasks
JavaJDBCNoHikariCPIndustry standard
GopgxYespgxpoolHigh performance

Production Tips

  1. Set idle_timeout on connections. Idle connections can go stale after a database restart or a network blip. Configure a timeout so the pool recycles them automatically.
  2. Use application_name for debugging. Set application_name in the connection string so you can identify your app in pg_stat_activity:
conn = psycopg2.connect(
    host="localhost",
    database="mydb",
    user="user",
    password="pass",
    application_name="my-api-server"
)
  1. Enable statement_timeout at the session level. It keeps runaway queries from consuming resources:
SET statement_timeout = '10s';
  1. Use COPY for bulk inserts. COPY is 10-100x faster than individual INSERT statements for large datasets:
import io
import psycopg2

buf = io.StringIO()
buf.write("1\talice@example.com\n")
buf.write("2\tbob@example.com\n")
buf.seek(0)

with psycopg2.connect("dbname=mydb user=user host=localhost") as conn:
    with conn.cursor() as cur:
        cur.copy_from(buf, "users", columns=("id", "email"))
  1. Monitor pool health. Track active connections, waiting threads, and connection lifetime. With HikariCP, use getHikariPoolMXBean() to expose metrics.

Frequently Asked Questions

What is the difference between psycopg2 and psycopg3?

psycopg2 is the mature, stable driver. psycopg3 (now just psycopg) adds async support, better type handling, and is the recommended choice for new projects.

How many connections should my pool have?

A good starting point is (2 x CPU cores) + effective_spindle_count for the database, divided by the number of app instances. Monitor pg_stat_activity and adjust.

Should I use sslmode=require or verify-full?

Go with verify-full when you've got the CA certificate on hand — it checks that the server really is who it claims to be. require encrypts the traffic but skips that identity check, so it only makes sense when you can't get hold of the CA chain. Avoid rejectUnauthorized: false in Node.js — it silently disables the same check.

How do I handle connection failures gracefully?

Implement retry logic with exponential backoff. In Python, use tenacity or a similar library. In Node.js, use p-retry. Always set a maximum retry count to avoid infinite loops:

import psycopg2
from tenacity import retry, stop_after_attempt, wait_exponential

DB_CONFIG = dict(
    host="localhost", database="mydb",
    user="user", password="pass", sslmode="prefer"
)

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10))
def query_with_retry(sql, params):
    with psycopg2.connect(**DB_CONFIG) as conn:
        with conn.cursor() as cur:
            cur.execute(sql, params)
            return cur.fetchall()
What is connection multiplexing?

Connection multiplexing allows multiple logical sessions to share a single database connection. PgBouncer provides this for PostgreSQL. Use transaction mode for most applications, where each transaction gets a connection from the pool.

How do I debug slow queries?

Enable log_min_duration_statement in PostgreSQL to log queries slower than a threshold:

ALTER SYSTEM SET log_min_duration_statement = '100ms';
SELECT pg_reload_conf();

Then check the PostgreSQL log file for slow query entries and use EXPLAIN ANALYZE to inspect the query plan. If the plan shows sequential scans on large tables, see the indexing strategies guide.