PostgreSQL Client for Constructive.io (pgsql-client)
pgsql-client is a PostgreSQL client utility specifically designed as a core component of the constructive-io ecosystem. It provides robust query helpers, integrated Row-Level Security (RLS) context management, and tools for database administration, emphasizing a database-first approach to application development. This library ships with comprehensive TypeScript types, enabling developers to build type-safe, modular PostgreSQL applications by treating the database as a version-controlled system. Its current stable version, 3.9.3, reflects ongoing development within the constructive-io framework. The project's release cadence is tied to the broader constructive-io ecosystem, which maintains active development. Key differentiators include its deep integration with RLS for fine-grained access control, support for modular database design via tools like `pgpm`, and rich developer tooling to streamline secure and scalable PostgreSQL backend development. It is important to distinguish this package from the `postgresql-client` NPM package, which was renamed to `postgrejs`.
Common errors
-
Error: RLS policy denied access for table "X" (or similar 'permission denied for relation')
cause The Row-Level Security (RLS) policies on the PostgreSQL server prevented the current user/role or session context from accessing the requested rows or table.fixReview your RLS policies on the PostgreSQL server. Ensure `client.setContext` is correctly populating the session variables that your RLS policies rely on (e.g., `current_setting('jwt.claims.user_id', true)`). -
Error: Cannot connect to database: password authentication failed for user "X"
cause The provided username, password, host, or port in the connection string is incorrect, or the database server is not reachable.fixVerify your `DATABASE_URL` environment variable or connection string. Check PostgreSQL server logs for connection attempts and ensure the database server is running and accessible from your application's host. -
TypeError: Cannot read properties of undefined (reading 'query')
cause The `client` object was not successfully initialized or connected before attempting to call `client.query()` or other methods. This can happen if `client.connect()` throws an error or is not awaited.fixEnsure proper error handling around `new Client()` and `await client.connect()`. Always check if the `client` object is defined and connected before performing database operations.
Warnings
- gotcha This `pgsql-client` refers to a utility within the `constructive-io` ecosystem (github.com/constructive-io/constructive) and is NOT the npm package `postgresql-client` which was renamed to `postgrejs`. Confusing these two can lead to incorrect installations and API mismatches.
- breaking Major version upgrades of PostgreSQL servers (e.g., from PG16 to PG17 or later) can introduce protocol changes, deprecated features, or data type incompatibilities. While `pgsql-client` aims for compatibility, always review its release notes against your PostgreSQL server version upgrades to avoid unexpected behavior.
- gotcha Incorrect or missing Row-Level Security (RLS) context can lead to authorization failures, either granting unintended access or blocking legitimate users from data. The `setContext` method is critical for enforcing RLS policies correctly.
- gotcha Connection pooling is vital for performance in production environments. While `pgsql-client` likely handles pooling internally or expects it to be configured, mismanaging connections (e.g., creating a new client for every query) can lead to resource exhaustion and degraded performance.
Install
-
npm install pgsql-client -
yarn add pgsql-client -
pnpm add pgsql-client
Imports
- Client
const Client = require('pgsql-client')import { Client } from 'pgsql-client' - ConnectionOptions
import { ConnectionOptions } from 'pgsql-client' - setContext
await client.setContext({...})
Quickstart
import { Client } from 'pgsql-client';
async function runPgsqlClientExample() {
// Ensure DATABASE_URL is set in your environment or provide a default
const connectionString = process.env.DATABASE_URL ?? 'postgresql://user:password@localhost:5432/mydb';
let client: Client | undefined;
try {
// Initialize and connect the PostgreSQL client
client = new Client(connectionString);
await client.connect();
console.log('Successfully connected to PostgreSQL database.');
// Set RLS context for the current session, crucial for secure data access
// In a real application, this context comes from an authenticated user's JWT claims or session data.
const currentUser = {
id: 'usr_abc123',
role: 'authenticated',
tenant_id: 'org_xyz'
};
await client.setContext({
role: currentUser.role,
'jwt.claims.user_id': currentUser.id,
'app.tenant_id': currentUser.tenant_id // Custom context variable for multi-tenancy
});
console.log(`RLS context set for user '${currentUser.id}' in tenant '${currentUser.tenant_id}'.`);
// Execute a query; RLS policies on the server will automatically filter results
// based on the 'app.tenant_id' set in the context.
const result = await client.query('SELECT id, name, description FROM products WHERE tenant_id = current_setting(\'app.tenant_id\', true)::text ORDER BY name LIMIT 3');
console.log('Query executed. Products (filtered by RLS):', result.rows);
} catch (error: any) {
console.error('Database operation failed:', error.message);
} finally {
if (client) {
// Always ensure the client connection is closed
await client.close();
console.log('PostgreSQL connection closed.');
}
}
}
runPgsqlClientExample();