Connect to PostgreSQL
How to connect to PostgreSQL databases in Python, JavaScript, and Java.
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.
Once connected, you can execute raw SQL queries or use an ORM for CRUD operations.
Variants
| Technology | Approach | Notes |
|---|---|---|
| Python | asyncpg | Async PostgreSQL driver for asyncio |
| JavaScript | pg-promise | Helper library with transactions and tasks |
| Java | HikariCP | High-performance JDBC connection pool |
What Works
- Use a connection pool in production — a fresh connection per request will exhaust
max_connectionsfast - Keep credentials in environment variables or a secrets manager, never in source
- Turn on SSL for production (
sslmode=verify-fullonce you’ve got the CA cert) - Always parameterize; values concatenated into SQL strings are how injections happen
- Close cursors and connections, or let context managers /
try-with-resourcesdo it
Common Mistakes
- Hardcoding database credentials in source code
- Opening a new connection per query instead of pooling
- Leaking connections until Postgres hits “too many connections” errors
- Turning off certificate verification in production (
rejectUnauthorized: false,sslmode=disable) - 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
| Technology | Driver | Async | Pooling | Notes |
|---|---|---|---|---|
| Python | psycopg2 | No | Manual or SimpleConnectionPool | Mature, stable |
| Python | psycopg3 | Yes | Built-in | Recommended for new projects |
| Python | asyncpg | Yes | Built-in | Fastest async driver |
| JavaScript | pg | Yes (Promise) | Pool built-in | Standard Node.js driver |
| JavaScript | pg-promise | Yes | Built-in | Extra helpers for tasks |
| Java | JDBC | No | HikariCP | Industry standard |
| Go | pgx | Yes | pgxpool | High performance |
Production Tips
- Set
idle_timeouton connections. Idle connections can go stale after a database restart or a network blip. Configure a timeout so the pool recycles them automatically. - Use
application_namefor debugging. Setapplication_namein the connection string so you can identify your app inpg_stat_activity:
conn = psycopg2.connect(
host="localhost",
database="mydb",
user="user",
password="pass",
application_name="my-api-server"
)
- Enable
statement_timeoutat the session level. It keeps runaway queries from consuming resources:
SET statement_timeout = '10s';
- Use
COPYfor bulk inserts.COPYis 10-100x faster than individualINSERTstatements 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"))
- 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.
Related Resources
Connect to MySQL
How to connect to MySQL in Python, JavaScript, and Java — with connection pooling, SSL, and prepared statements that hold up in production.
RecipeConnect to Redis
How to connect to Redis in Python, JavaScript, and Java — with AUTH, TLS, pooling, and the core data types you'll actually use.
RecipeExecute Raw SQL
How to execute raw SQL queries safely with parameterized statements.
RecipeORM CRUD with SQLAlchemy, Prisma & Hibernate
How to run create, read, update, and delete operations with SQLAlchemy, Prisma, and Hibernate — the same data model in Python, JavaScript, and Java.
GuideDatabase Indexing Strategies — From B-Trees to BRIN
A practical guide to database indexes: B-Trees, Hash, GIN, GiST, BRIN, and partial indexes. Learn when to use each and how to avoid common indexing mistakes.
GuideSQL Joins — Visual Guide with Examples
A visual guide to SQL joins: INNER, LEFT, RIGHT, FULL OUTER, CROSS, and SELF joins with practical examples, performance tips, and common pitfalls.