pg
>=8.0.0postconditions27functions5last verified2026-06-23coverage score83%Postconditions: what we check
- query · syntax-errorerrorWhenSQL syntax error (42601)Throws
Error with code '42601'Required handlingCaller MUST validate SQL syntax before execution. This indicates a SQL syntax error - DO NOT retry. Check error.position for location of syntax error in query.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - query · unique-violationerrorWhenUnique constraint violation (23505)Throws
Error with code '23505'Required handlingCaller MUST handle duplicate key violations gracefully. Extract constraint name from error.constraint. DO NOT retry without changing the unique field value.costhighin prodimmediate exceptionusers seelost datavisibilityvisibleSources[1] - query · foreign-key-violationerrorWhenForeign key constraint violation (23503)Throws
Error with code '23503'Required handlingCaller MUST verify referenced record exists before insertion. Extract constraint and table from error.constraint and error.table. This indicates data integrity issue - DO NOT retry.costhighin prodimmediate exceptionusers seelost datavisibilityvisibleSources[1] - query · not-null-violationerrorWhenNOT NULL constraint violation (23502)Throws
Error with code '23502'Required handlingCaller MUST provide all required fields. Extract column name from error.column. Validate data completeness before query execution.costmediumin prodimmediate exceptionusers seelost datavisibilityvisibleSources[1] - query · connection-errorerrorWhenConnection to database failedThrows
Error with code 'ECONNREFUSED' or similar network errorsRequired handlingCaller MUST handle connection failures separately from query errors. Common causes: database down, wrong host/port, network issues. Implement retry with exponential backoff for transient connection issues. Alert operations if connection errors persist.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - query · undefined-tableerrorWhenTable or view does not exist (42P01)Throws
Error with code '42P01'Required handlingCaller MUST verify table exists before querying. Check error.message for table name. DO NOT retry - this indicates schema mismatch or missing migration.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - query · null-undefined-query-throwserrorWhenclient.query() or pool.query() called with null/undefined first argumentThrows
TypeError with message 'Client was passed a null or undefined query'Required handlingCaller MUST validate that the query string/config is non-null before invoking query(). Common cause: building dynamic SQL from a conditional that may yield undefined (e.g. `const sql = condition ? buildQuery() : undefined; await client.query(sql)`). This is a synchronous TypeError thrown before any Promise resolution — it WILL NOT be caught by `.catch()` on the returned promise if the caller forgot to await inside try/catch. Wrap query() invocations in try/catch when the query source could be undefined.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - query · pool-query-function-arg-rejectserrorWhenpool.query() called with a function as the first argumentThrows
Error with message 'Passing a function as the first parameter to pool.query is not supported'Required handlingCaller MUST pass a query string or QueryConfig object as the first argument to pool.query() — never a function. Common cause: passing a query-builder factory by reference instead of calling it. Pattern check: `pool.query(buildQuery, ...)` should be `pool.query(buildQuery(), ...)`. The error rejects the returned Promise — must be handled in catch block.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - query · query-read-timeouterrorWhenQuery exceeds query_timeout (per-query or client-level) millisecondsThrows
Error with message 'Query read timeout'Required handlingCaller MUST handle query timeout errors distinctly from connection errors. When query_timeout is configured (on Client config or per-query via QueryConfig), long-running queries that exceed the timeout reject with 'Query read timeout'. DO NOT blindly retry — the query may still be running on the database and a retry can cause duplicate work or duplicate writes. Cancel the query at the PostgreSQL level via pg_cancel_backend() if retry is required. For idempotent read queries, retry with shorter timeout is acceptable.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - query · database-error-canonical-typewarningWhenPostgres server-side error (constraint violation, syntax error, type error, etc.)Throws
DatabaseError (from 'pg-protocol', re-exported by pg) with .code property containing SQLSTATERequired handlingCaller SHOULD use `error instanceof DatabaseError` to distinguish Postgres-level errors from client-side errors (TypeError, network errors). DatabaseError exposes structured fields not available on generic Error: .code (5-char SQLSTATE), .detail, .hint, .position (for syntax errors), .constraint, .table, .column, .schema. Generic `catch (error)` without instance check loses this structured context. Pattern: try { await client.query(...); } catch (err) { if (err instanceof DatabaseError) { // handle by err.code (e.g. '23505' unique violation) } else { // network / type error } }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - connect · pool-exhaustederrorWhenAll clients in pool are busy and connectionTimeoutMillis exceededThrows
Error with message 'timeout exceeded when trying to connect'Required handlingCaller MUST handle pool exhaustion errors. Root causes: 1. Clients not released (forgot client.release()) 2. Pool size too small for load 3. Queries taking too long Implement proper client.release() in finally blocks. Monitor pool metrics to identify leaks.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - connect · connection-errorerrorWhenCannot establish database connectionThrows
Error with code 'ECONNREFUSED', 'ETIMEDOUT', etc.Required handlingCaller MUST handle connection failures. Implement retry with exponential backoff. Check database server status. Verify connection string credentials.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - connect · client-not-releasederrorWhenCaller forgets to release client back to poolThrows
No error thrown, but pool becomes exhausted over timeRequired handlingCaller MUST ALWAYS release clients in a finally block: ``` const client = await pool.connect(); try { await client.query(...); } finally { client.release(); } ``` CRITICAL: This is the #1 production bug (affects 30% of mid-sized projects). Failure to release causes pool exhaustion and application hangs. Silent failure until complete outage.costcriticalin proddelayed failureusers seeservice unavailablevisibilityvisibleSources[10] - connect · client-reuse-after-connecterrorWhenClient.connect() called a second time on the same Client instanceThrows
Error with message 'Client has already been connected. You cannot reuse a client.'Required handlingCaller MUST construct a NEW Client instance for each connection cycle. Calling .connect() twice on the same Client (even after .end()) rejects with this error. Common cause: connection-retry logic that loops on the same client instance after end(). CORRECT pattern: on retry, instantiate `const client = new Client(config)` then await client.connect(). For pooled scenarios, use Pool which manages the Client lifecycle internally.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - connect · pool-connect-after-enderrorWhenPool.connect() called after Pool.end() has been initiatedThrows
Error with message 'Cannot use a pool after calling end on the pool'Required handlingCaller MUST stop routing new request handlers before calling pool.end(). Pattern: set a `shuttingDown` flag in the SIGTERM/SIGINT handler, reject incoming requests while shuttingDown is true, then call pool.end(). Without this guard, any in-flight request handler that calls pool.connect() during graceful-shutdown drain throws this error and the request fails. This is distinct from the `pool-query-after-end` postcondition on `end` — that one covers pool.query(); this one covers pool.connect() specifically.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - connect · pool-onconnect-hook-failserrorWhenPoolConfig.onConnect callback throws or rejects during connection setupThrows
The hook's own error (any Error subclass) rejected from Pool.connect()Required handlingCaller MUST handle errors from pool.connect() even when an onConnect hook is configured — a failing hook propagates as a pool.connect() rejection. The onConnect hook is invoked after the underlying TCP connect succeeds but before the client is returned. Common failure modes inside onConnect: (1) a SET command fails (`SET search_path TO ...` with an invalid schema), (2) a session-config query times out, (3) the hook awaits an external service that returns 500. The pool destroys the client on hook failure (client.end()) — partial initialization is not retained. Implement retry logic around pool.connect() calls whose onConnect performs network I/O.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - connect · connection-terminated-during-timeouterrorWhenTCP/TLS handshake for a new pool client exceeds connectionTimeoutMillisThrows
Error with message 'Connection terminated due to connection timeout' (with original error attached as .cause)Required handlingCaller MUST distinguish this from `pool-exhausted` (which fires when an existing idle client is unavailable). This timeout fires during the actual TCP/TLS handshake when establishing a new connection — typically caused by network latency, firewall delays, or a slow PostgreSQL bootstrap (slow SSL negotiation on PG 17+ direct sslnegotiation). Inspect error.cause for the underlying connect error to diagnose. Treat as a transient error and retry with backoff, but escalate to ops if persistent — usually indicates network/infrastructure issue, not pool sizing.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - connect · sslnegotiation-direct-requires-sslerrorWhenPool/Client constructed with sslnegotiation: 'direct' but ssl is falsyThrows
Error with message 'sslnegotiation=direct requires SSL to be enabled' (or 'Invalid sslnegotiation value: ...' for unknown values)Required handlingCaller MUST set ssl: true (or an ssl ConnectionOptions object) when using sslnegotiation: 'direct'. Direct SSL negotiation requires TLS — there is no plaintext mode. This is a configuration-time error: the Pool or Client constructor throws synchronously, so the failure occurs at app startup, not on first connect(). When upgrading to pg@8.22.0+ and PostgreSQL 17+ for the faster TLS handshake, audit existing PoolConfig/ClientConfig to ensure ssl is set wherever sslnegotiation is. Equally, 'sslnegotiation' values other than 'postgres' or 'direct' throw a similar Invalid-value error.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - end · pool-end-called-twiceerrorWhenPool.end() called more than once on the same pool instanceThrows
Error with message 'Called end on pool more than once'Required handlingCaller MUST guard against double-end by checking a boolean flag before calling pool.end(). Common in serverless functions with multiple shutdown signal handlers. Pattern: if (!isShuttingDown) { isShuttingDown = true; await pool.end(); } Failure to guard causes an unhandled rejection on the second call.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - end · pool-query-after-enderrorWhenPool.connect() or Pool.query() called after Pool.end() has completedThrows
Error with message 'Cannot use a pool after calling end on the pool'Required handlingCaller MUST stop routing new requests before initiating pool shutdown. Implement graceful drain: stop accepting requests → wait for in-flight to complete → then call pool.end(). Never call pool.end() in the middle of active request handling without draining first.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - end · client-end-connection-terminatedwarningWhenQueries in-flight when Client.end() is calledThrows
Error with message 'Client was closed and is not queryable'Required handlingCaller MUST ensure no queries are in-flight before calling client.end(). For standalone client usage, always await all queries to completion before calling end(). Use try/finally to ensure end() is called after query completion.costhighin prodimmediate exceptionusers seelost datavisibilityvisible - release · double-release-throwserrorWhenclient.release() called more than once on the same PoolClientThrows
Error with message 'Release called on client which has already been released to the pool.'Required handlingCaller MUST call release() exactly once. ALWAYS use finally block exclusively: const client = await pool.connect(); try { ... } finally { client.release(); } NEVER call release() in both catch and finally — the catch error path goes through the finally block, causing a double-release. If a transaction error occurred, call client.release(err) once in finally.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - release · release-with-error-destroys-clientwarningWhenclient.release(err) called with an error after a failed transactionThrows
No error thrown — client is destroyed instead of returned to poolRequired handlingCaller SHOULD pass the error to release() when the client was used in a failed transaction: client.release(err). This destroys the connection rather than returning a tainted connection (with an open transaction or held locks) to the pool. Forgetting to pass the error can cause the next user of that pooled connection to inherit transaction state.costhighin prodimmediate exceptionusers seelost datavisibilityvisible - transaction · transaction-not-rolled-back-on-errorerrorWhenQuery inside BEGIN/COMMIT transaction block throws and ROLLBACK is not issuedThrows
No error from pg — but PostgreSQL holds row locks until session disconnectsRequired handlingCaller MUST issue ROLLBACK in the catch block whenever a transaction is in progress. REQUIRED pattern: try { await client.query('BEGIN'); ... await client.query('COMMIT'); } catch (e) { await client.query('ROLLBACK'); throw e; } finally { client.release(); } Failure to ROLLBACK leaves open locks, causing other queries to block waiting for those rows. Can cascade into application-wide deadlock.costcriticalin proddelayed failureusers seeservice unavailablevisibilityvisible - transaction · transaction-on-pool-queryerrorWhenpool.query() used for BEGIN/COMMIT/ROLLBACK instead of a dedicated clientThrows
No error thrown — but transaction silently spans different connectionsRequired handlingCaller MUST obtain a dedicated client via pool.connect() for all transaction work. NEVER use pool.query() for BEGIN/COMMIT/ROLLBACK — each call may hit a different connection, making the transaction a no-op. CORRECT: const client = await pool.connect(); await client.query('BEGIN'); ... WRONG: await pool.query('BEGIN'); await pool.query(...); await pool.query('COMMIT');costcriticalin prodsilent failureusers seelost datavisibilitysilentSources[12] - transaction · transaction-deadlockerrorWhenTwo concurrent transactions deadlock — PostgreSQL code 40P01Throws
DatabaseError with error.code === '40P01' (deadlock_detected)Required handlingCaller MUST catch DatabaseError with code '40P01' and retry the entire transaction from BEGIN. DO NOT retry individual queries — the whole transaction context is invalidated. Implement exponential backoff with jitter between retries. Check error.code to distinguish deadlock from other errors.costhighin prodimmediate exceptionusers seelost datavisibilityvisible - transaction · transaction-serialization-failureerrorWhenSERIALIZABLE transaction conflicts — PostgreSQL code 40001Throws
DatabaseError with error.code === '40001' (serialization_failure)Required handlingCaller MUST catch DatabaseError with code '40001' and retry the entire transaction from BEGIN. Serialization failures are expected in SERIALIZABLE isolation — they are not bugs but rather PostgreSQL enforcing correctness. Implement retry with backoff. Only applies when using SERIALIZABLE isolation.costhighin prodimmediate exceptionusers seelost datavisibilityvisible
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [1]postgresql.org/docs/current/errcodes-appendix.htmlErrcodes Appendix
- [2]node-postgres.comnode-postgres.com
- [5]node-postgres.com/apis/poolPool
- [6]node-postgres.com/announcementsAnnouncements
- [8]node-postgres.com/apis/clientClient
- [9]node-postgres.com/features/poolingPooling
- [12]node-postgres.com/features/transactionsTransactions
- [13]postgresql.org/docs/current/sql-rollback.htmlSql Rollback
- [14]postgresql.org/docs/current/explicit-locking.htmlExplicit Locking
- [15]postgresql.org/docs/current/transaction-iso.htmlTransaction Iso
- [3]github.com/brianc/node-postgres/blobbrianc/node-postgres · client.js
- [4]github.com/brianc/node-postgres/blobbrianc/node-postgres · index.js
- [7]github.com/brianc/node-postgres/blobbrianc/node-postgres · messages.ts
- [11]github.com/brianc/node-postgres/blobbrianc/node-postgres · connection-parameters.js
- [10]github.com/brianc/node-postgres/issuesbrianc/node-postgres issue #1882
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
PostgreSQL (pg) Nark profile - Sources
Official Documentation
node-postgres (pg) Documentation
- Main Documentation: https://node-postgres.com/
- Pooling Guide: https://node-postgres.com/features/pooling
- Pool API: https://node-postgres.com/apis/pool
- Client API: https://node-postgres.com/apis/client
- Queries: https://node-postgres.com/features/queries
- SSL Configuration: https://node-postgres.com/features/ssl
PostgreSQL Official Documentation
- Error Codes (SQLSTATE): https://www.postgresql.org/docs/current/errcodes-appendix.html
- Error Handling: https://www.postgresql.org/docs/current/ecpg-errors.html
- Security Information: https://www.postgresql.org/support/security/
Error Codes (SQLSTATE)
PostgreSQL uses SQLSTATE error codes defined by the SQL standard. The pg library exposes these via the error.code property.
Common Error Classes
Class 23 - Integrity Constraint Violation
- 23505 -
unique_violation: Unique constraint violated (duplicate key) - 23503 -
foreign_key_violation: Foreign key constraint violated - 23502 -
not_null_violation: NOT NULL constraint violated - 23514 -
check_violation: CHECK constraint violated
Class 42 - Syntax Error or Access Rule Violation
- 42601 -
syntax_error: SQL syntax error - 42P01 -
undefined_table: Table does not exist - 42703 -
undefined_column: Column does not exist - 42883 -
undefined_function: Function does not exist
Class 28 - Invalid Authorization Specification
- 28P01 -
invalid_password: Authentication failed - 28000 -
invalid_authorization_specification: SSL required but not used
Class 3D - Invalid Catalog Name
- 3D000 -
invalid_catalog_name: Database does not exist
Source: https://www.postgresql.org/docs/current/errcodes-appendix.html
Using Error Constants
Instead of hardcoding error codes, use helper libraries:
pg-error-constants (recommended):
import { UNIQUE_VIOLATION, FOREIGN_KEY_VIOLATION } from 'pg-error-constants';
if (error.code === UNIQUE_VIOLATION) {
// Handle duplicate key
}
pg-error (alternative): Parses PostgreSQL ErrorResponse format with human-readable field names.
Sources:
Connection Pooling
Pool Configuration
node-postgres ships with built-in connection pooling via the pg-pool module.
Key Configuration Options:
max: Maximum number of clients in pool (default: 10)connectionTimeoutMillis: Max wait time for client checkout (default: 0 = no timeout)idleTimeoutMillis: How long client can be idle before being closed (default: 10000)maxUses: Maximum number of times to use a client before closing (optional)
Example:
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'mydb',
user: 'user',
password: 'password',
max: 20, // Max 20 connections
idleTimeoutMillis: 30000, // Close idle clients after 30s
connectionTimeoutMillis: 2000, // Wait max 2s for connection
});
Source: https://node-postgres.com/apis/pool
Critical Pool Error Handling
MUST add error listener to pool:
pool.on('error', (err, client) => {
console.error('Unexpected error on idle client', err);
// Client is automatically terminated and removed from pool
});
Why critical: If a pool emits an error event and no listeners are added, Node.js will emit an uncaught error and potentially crash the process.
Source: https://node-postgres.com/apis/pool
Client Release Management
Most critical production issue: Forgetting to release clients.
Problem: If you forget to call client.release(), your application will quickly exhaust available clients in the pool. All further calls to pool.connect() will timeout or hang indefinitely.
Solution - ALWAYS use try/finally:
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
return result.rows;
} finally {
client.release(); // ALWAYS release, even on error
}
Alternative - Use pool.query() for simple queries:
// Automatically checks out and releases client
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
When to use Pool.connect() vs Pool.query():
- Use
pool.connect(): For transactions or multiple sequential queries - Use
pool.query(): For single queries (automatic release)
Sources:
Common Production Issues
Issue 1: Pool Exhaustion (MOST COMMON - 30% of Projects)
Severity: Critical - Application hangs Prevalence: Affects approximately 30% of mid-sized projects
Symptoms:
timeout exceeded when trying to connecterrors- Application hangs waiting for database connections
- All pool clients show as busy
Root Causes:
- Forgot to release client: Most common cause (missing client.release() in finally)
- Pool size too small: Not enough connections for load
- Long-running queries: Queries hold connections too long
- Connection leaks: Clients not released on error paths
Solutions:
- ALWAYS use try/finally with client.release()
- Use pool.query() for simple queries
- Monitor pool metrics:
console.log('Total clients:', pool.totalCount); console.log('Idle clients:', pool.idleCount); console.log('Waiting clients:', pool.waitingCount); - Increase pool size if needed:
max: 20 - Configure connection timeout:
connectionTimeoutMillis: 5000
Sources:
- GitHub Issue: Pool Exhaustion
- GitHub Issue: Connection Leaks
- GitHub Issue: Pool Leaking #1777
- Connection Pooling Guide (2026)
Issue 2: SQL Injection Vulnerabilities
Severity: Critical - Security risk
Problem: Using string concatenation instead of parameterized queries
Vulnerable Code:
// ❌ DANGEROUS: SQL injection vulnerability
const query = `SELECT * FROM users WHERE email = '${email}'`;
await pool.query(query);
Secure Code:
// ✅ SAFE: Parameterized query
await pool.query('SELECT * FROM users WHERE email = $1', [email]);
Additional Protection: Recent CVEs show even libpq quoting functions can have injection risks. Always use parameterized queries.
Sources:
Issue 3: Uncaught Pool Errors
Severity: High - Process crash
Problem: Not adding error listener to pool causes Node.js to crash on background errors
Solution:
pool.on('error', (err, client) => {
console.error('Unexpected error on idle client', err);
// Log to monitoring system
// Client is automatically removed from pool
});
Common background errors:
- Network disconnections
- Database restart
- Connection timeouts
Source: https://node-postgres.com/apis/pool
Issue 4: Connection String Misconfigurations
Severity: High - Connection failures
Common issues:
- Wrong host/port
- Missing database name
- Invalid credentials
- SSL required but not configured
Error codes:
ECONNREFUSED: Server not running or wrong host/port28P01: Authentication failed (invalid password)3D000: Database does not exist28000: SSL required
Solutions:
- Use connection strings for simplicity:
postgresql://user:pass@host:5432/db - Enable SSL for production:
ssl: { rejectUnauthorized: false } - Validate configuration on startup
Source: https://node-postgres.com/features/ssl
Issue 5: Missing Error Code Handling
Severity: Medium - Poor error messages
Problem: Not checking error.code to provide specific error handling
Solution:
try {
await pool.query('INSERT INTO users (email) VALUES ($1)', [email]);
} catch (error) {
if (error.code === '23505') {
// Unique violation - user already exists
throw new Error(`User with email ${email} already exists`);
} else if (error.code === '23502') {
// Not null violation
throw new Error(`Missing required field: ${error.column}`);
}
throw error; // Re-throw unexpected errors
}
Useful error properties:
error.code: SQLSTATE codeerror.constraint: Constraint name (for violations)error.table: Table nameerror.column: Column nameerror.position: Error position in query (for syntax errors)
Sources:
Security Advisories
Last Checked: 2026-02-26
npm pg Package CVE
CVE-2017-16082 - Arbitrary Code Execution
- Impact: Specially crafted column names could execute arbitrary code during query result parsing
- Severity: HIGH
- Affected Versions: All versions < 7.2.0
- Fixed Version: 7.2.0+
- Current Status: ✅ All modern versions (8.x) are safe
- Minimum Safe Version: 8.0.0 (recommended)
- Source: Snyk Security Advisory
PostgreSQL Server CVEs (2025-2026)
These affect PostgreSQL server, not the npm pg package specifically, but are relevant for node-postgres users:
CVE-2025-8715 - pg_dump Newline Injection (Critical)
- Impact: Arbitrary code execution via malicious object names in dumps
- Severity: High
- Mitigation: Update PostgreSQL server to patched version
- Source: CVE-2025-8715
CVE-2025-8714 - pg_dump Superuser Code Injection (Critical)
- Impact: Malicious superuser can inject code via psql meta-commands
- Severity: High
- Mitigation: Trust only dumps from trusted sources
- Source: CVE-2025-8714
CVE-2026-2005 - pgcrypto Heap Buffer Overflow (Critical)
- Impact: Arbitrary code execution in pgcrypto extension
- Severity: Critical
- Mitigation: Update PostgreSQL server
- Source: CVE-2026-2005
CVE-2025-1094 - libpq Quoting Syntax Neutralization Failure
- Impact: SQL injection in certain usage patterns
- Severity: Medium
- Mitigation: Always use parameterized queries, not string escaping
- Source: CVE-2025-1094
CVE Statistics:
- 2024: Multiple vulnerabilities
- 2025: 9 vulnerabilities
- 2026: 0 vulnerabilities (as of Feb 2026)
Source: PostgreSQL Security Vulnerabilities
Summary:
- 1 direct CVE in pg package (CVE-2017-16082, fixed in 7.2.0+)
- Multiple PostgreSQL server CVEs (affect server, not pg package directly)
- All production apps should use pg@8.0.0 or later
Recommendation: Keep both PostgreSQL server and npm pg package updated.
Best Practices Summary
1. Always Use Parameterized Queries
// ✅ CORRECT
await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
// ❌ WRONG - SQL injection vulnerability
await pool.query(`SELECT * FROM users WHERE id = '${userId}'`);
2. Always Release Clients
// ✅ CORRECT - Using try/finally
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('INSERT INTO users (name) VALUES ($1)', ['Alice']);
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release(); // ALWAYS release
}
3. Handle Pool Errors
// ✅ REQUIRED
pool.on('error', (err, client) => {
console.error('Unexpected error on idle client', err);
});
4. Check Error Codes
// ✅ RECOMMENDED
import { UNIQUE_VIOLATION, FOREIGN_KEY_VIOLATION } from 'pg-error-constants';
try {
await pool.query('INSERT INTO users (email) VALUES ($1)', [email]);
} catch (error) {
if (error.code === UNIQUE_VIOLATION) {
return { error: 'Email already exists' };
}
throw error;
}
5. Configure Pool Properly
// ✅ PRODUCTION CONFIGURATION
const pool = new Pool({
max: 20, // Adjust based on load
idleTimeoutMillis: 30000, // Close idle connections
connectionTimeoutMillis: 2000, // Don't wait forever
});
// Monitor pool health
setInterval(() => {
console.log('Pool status:', {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
});
}, 60000);
Verification Date
Last Verified: 2026-02-26 pg Package Version: 8.18.0 (latest stable) PostgreSQL Version: 12+ Documentation Version: Current as of February 2026 Downloads: 16,050,446 per week Status: Production Ready ✅