PostgreSQL with node-postgres
PostgreSQL is the most popular open-source relational database, and pg (node-postgres) is the standard driver for using it from Node. Working directly with pg means writing real SQL with parameterized queries — the foundation every Postgres ORM is built on. This page covers pooling, parameterized queries, reading results, transactions, and the Postgres-specific features worth knowing.
Setup and a connection pool
npm install pg
db.js — one pool for the whole app
import { Pool } from 'pg'
export const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // max connections in the pool
idleTimeoutMillis: 30_000,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: true } : false,
})
// Helper that borrows + returns a connection automatically:
export const query = (text, params) => pool.query(text, params)Parameterized queries — always
// Placeholders are $1, $2, ... with a separate values array:
const { rows } = await pool.query(
'SELECT id, name, email FROM users WHERE email = $1 AND active = $2',
[req.body.email, true],
)
const user = rows[0] // undefined if no match
// NEVER interpolate user input into the SQL string:
// `... WHERE email = '${req.body.email}'` ← SQL INJECTIONReading the result object
const result = await pool.query('SELECT * FROM users WHERE active = $1', [true])
result.rows // → array of row objects: [{ id: 1, name: 'Ada' }, ...]
result.rowCount // → number of rows affected/returned
result.fields // → column metadata
// INSERT ... RETURNING gets the new row back in one round-trip:
const { rows: [created] } = await pool.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
['Ada', 'ada@x.com'],
)Transactions — borrow a single client
// A multi-statement transaction must use ONE connection, not pool.query:
const client = await pool.connect()
try {
await client.query('BEGIN')
await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [100, 1])
await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [100, 2])
await client.query('COMMIT')
} catch (err) {
await client.query('ROLLBACK') // undo everything on any failure
throw err
} finally {
client.release() // ALWAYS return the connection to the pool
}Common Postgres errors to map
Error code | Meaning | Map to |
|---|---|---|
| unique_violation (duplicate) |
|
| foreign_key_violation |
|
| not_null_violation |
|
| invalid_text_representation (bad type) |
|
| check_violation |
|
Postgres features worth using
JSONB— store and query JSON columns; relational and document where useful.Arrays — native array columns (
text[]), queryable withANY/@>.Rich types —
uuid,timestamptz,numeric(exact decimals for money), enums.Constraints & indexes —
UNIQUE,CHECK, foreign keys, partial andGINindexes.Upsert —
INSERT ... ON CONFLICT ... DO UPDATEfor insert-or-update in one statement.