azure-postgres-ts

Azure PostgreSQL for TypeScript (node-postgres)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "azure-postgres-ts" with this command: npx skills add claudedjale/skillset/claudedjale-skillset-azure-postgres-ts

Azure PostgreSQL for TypeScript (node-postgres)

Connect to Azure Database for PostgreSQL Flexible Server using the pg (node-postgres) package with support for password and Microsoft Entra ID (passwordless) authentication.

Installation

npm install pg @azure/identity npm install -D @types/pg

Environment Variables

Required

AZURE_POSTGRESQL_HOST=<server>.postgres.database.azure.com AZURE_POSTGRESQL_DATABASE=<database> AZURE_POSTGRESQL_PORT=5432

For password authentication

AZURE_POSTGRESQL_USER=<username> AZURE_POSTGRESQL_PASSWORD=<password>

For Entra ID authentication

AZURE_POSTGRESQL_USER=<entra-user>@<server> # e.g., user@contoso.com AZURE_POSTGRESQL_CLIENTID=<managed-identity-client-id> # For user-assigned identity

Authentication

Option 1: Password Authentication

import { Client, Pool } from "pg";

const client = new Client({ host: process.env.AZURE_POSTGRESQL_HOST, database: process.env.AZURE_POSTGRESQL_DATABASE, user: process.env.AZURE_POSTGRESQL_USER, password: process.env.AZURE_POSTGRESQL_PASSWORD, port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432, ssl: { rejectUnauthorized: true } // Required for Azure });

await client.connect();

Option 2: Microsoft Entra ID (Passwordless) - Recommended

import { Client, Pool } from "pg"; import { DefaultAzureCredential } from "@azure/identity";

// For system-assigned managed identity const credential = new DefaultAzureCredential();

// For user-assigned managed identity // const credential = new DefaultAzureCredential({ // managedIdentityClientId: process.env.AZURE_POSTGRESQL_CLIENTID // });

// Acquire access token for Azure PostgreSQL const tokenResponse = await credential.getToken( "https://ossrdbms-aad.database.windows.net/.default" );

const client = new Client({ host: process.env.AZURE_POSTGRESQL_HOST, database: process.env.AZURE_POSTGRESQL_DATABASE, user: process.env.AZURE_POSTGRESQL_USER, // Entra ID user password: tokenResponse.token, // Token as password port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432, ssl: { rejectUnauthorized: true } });

await client.connect();

Core Workflows

  1. Single Client Connection

import { Client } from "pg";

const client = new Client({ host: process.env.AZURE_POSTGRESQL_HOST, database: process.env.AZURE_POSTGRESQL_DATABASE, user: process.env.AZURE_POSTGRESQL_USER, password: process.env.AZURE_POSTGRESQL_PASSWORD, port: 5432, ssl: { rejectUnauthorized: true } });

try { await client.connect();

const result = await client.query("SELECT NOW() as current_time"); console.log(result.rows[0].current_time); } finally { await client.end(); // Always close connection }

  1. Connection Pool (Recommended for Production)

import { Pool } from "pg";

const pool = new Pool({ host: process.env.AZURE_POSTGRESQL_HOST, database: process.env.AZURE_POSTGRESQL_DATABASE, user: process.env.AZURE_POSTGRESQL_USER, password: process.env.AZURE_POSTGRESQL_PASSWORD, port: 5432, ssl: { rejectUnauthorized: true },

// Pool configuration max: 20, // Maximum connections in pool idleTimeoutMillis: 30000, // Close idle connections after 30s connectionTimeoutMillis: 10000 // Timeout for new connections });

// Query using pool (automatically acquires and releases connection) const result = await pool.query("SELECT * FROM users WHERE id = $1", [userId]);

// Explicit checkout for multiple queries const client = await pool.connect(); try { const res1 = await client.query("SELECT * FROM users"); const res2 = await client.query("SELECT * FROM orders"); } finally { client.release(); // Return connection to pool }

// Cleanup on shutdown await pool.end();

  1. Parameterized Queries (Prevent SQL Injection)

// ALWAYS use parameterized queries - never concatenate user input const userId = 123; const email = "user@example.com";

// Single parameter const result = await pool.query( "SELECT * FROM users WHERE id = $1", [userId] );

// Multiple parameters const result = await pool.query( "INSERT INTO users (email, name, created_at) VALUES ($1, $2, NOW()) RETURNING *", [email, "John Doe"] );

// Array parameter const ids = [1, 2, 3, 4, 5]; const result = await pool.query( "SELECT * FROM users WHERE id = ANY($1::int[])", [ids] );

  1. Transactions

const client = await pool.connect();

try { await client.query("BEGIN");

const userResult = await client.query( "INSERT INTO users (email) VALUES ($1) RETURNING id", ["user@example.com"] ); const userId = userResult.rows[0].id;

await client.query( "INSERT INTO orders (user_id, total) VALUES ($1, $2)", [userId, 99.99] );

await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK"); throw error; } finally { client.release(); }

  1. Transaction Helper Function

async function withTransaction<T>( pool: Pool, fn: (client: PoolClient) => Promise<T> ): Promise<T> { const client = await pool.connect(); try { await client.query("BEGIN"); const result = await fn(client); await client.query("COMMIT"); return result; } catch (error) { await client.query("ROLLBACK"); throw error; } finally { client.release(); } }

// Usage const order = await withTransaction(pool, async (client) => { const user = await client.query( "INSERT INTO users (email) VALUES ($1) RETURNING *", ["user@example.com"] ); const order = await client.query( "INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING *", [user.rows[0].id, 99.99] ); return order.rows[0]; });

  1. Typed Queries with TypeScript

import { Pool, QueryResult } from "pg";

interface User { id: number; email: string; name: string; created_at: Date; }

// Type the query result const result: QueryResult<User> = await pool.query<User>( "SELECT * FROM users WHERE id = $1", [userId] );

const user: User | undefined = result.rows[0];

// Type-safe insert async function createUser( pool: Pool, email: string, name: string ): Promise<User> { const result = await pool.query<User>( "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *", [email, name] ); return result.rows[0]; }

Pool with Entra ID Token Refresh

For long-running applications, tokens expire and need refresh:

import { Pool, PoolConfig } from "pg"; import { DefaultAzureCredential, AccessToken } from "@azure/identity";

class AzurePostgresPool { private pool: Pool | null = null; private credential: DefaultAzureCredential; private tokenExpiry: Date | null = null; private config: Omit<PoolConfig, "password">;

constructor(config: Omit<PoolConfig, "password">) { this.credential = new DefaultAzureCredential(); this.config = config; }

private async getToken(): Promise<string> { const tokenResponse = await this.credential.getToken( "https://ossrdbms-aad.database.windows.net/.default" ); this.tokenExpiry = new Date(tokenResponse.expiresOnTimestamp); return tokenResponse.token; }

private isTokenExpired(): boolean { if (!this.tokenExpiry) return true; // Refresh 5 minutes before expiry return new Date() >= new Date(this.tokenExpiry.getTime() - 5 * 60 * 1000); }

async getPool(): Promise<Pool> { if (this.pool && !this.isTokenExpired()) { return this.pool; }

// Close existing pool if token expired
if (this.pool) {
  await this.pool.end();
}

const token = await this.getToken();
this.pool = new Pool({
  ...this.config,
  password: token
});

return this.pool;

}

async query<T>(text: string, params?: any[]): Promise<QueryResult<T>> { const pool = await this.getPool(); return pool.query<T>(text, params); }

async end(): Promise<void> { if (this.pool) { await this.pool.end(); this.pool = null; } } }

// Usage const azurePool = new AzurePostgresPool({ host: process.env.AZURE_POSTGRESQL_HOST!, database: process.env.AZURE_POSTGRESQL_DATABASE!, user: process.env.AZURE_POSTGRESQL_USER!, port: 5432, ssl: { rejectUnauthorized: true }, max: 20 });

const result = await azurePool.query("SELECT NOW()");

Error Handling

import { DatabaseError } from "pg";

try { await pool.query("INSERT INTO users (email) VALUES ($1)", [email]); } catch (error) { if (error instanceof DatabaseError) { switch (error.code) { case "23505": // unique_violation console.error("Duplicate entry:", error.detail); break; case "23503": // foreign_key_violation console.error("Foreign key constraint failed:", error.detail); break; case "42P01": // undefined_table console.error("Table does not exist:", error.message); break; case "28P01": // invalid_password console.error("Authentication failed"); break; case "57P03": // cannot_connect_now (server starting) console.error("Server unavailable, retry later"); break; default: console.error(PostgreSQL error ${error.code}: ${error.message}); } } throw error; }

Connection String Format

// Alternative: Use connection string const pool = new Pool({ connectionString: postgres://${user}:${password}@${host}:${port}/${database}?sslmode=require });

// With SSL required (Azure) const connectionString = postgres://user:password@server.postgres.database.azure.com:5432/mydb?sslmode=require;

Pool Events

const pool = new Pool({ /* config */ });

pool.on("connect", (client) => { console.log("New client connected to pool"); });

pool.on("acquire", (client) => { console.log("Client checked out from pool"); });

pool.on("release", (err, client) => { console.log("Client returned to pool"); });

pool.on("remove", (client) => { console.log("Client removed from pool"); });

pool.on("error", (err, client) => { console.error("Unexpected pool error:", err); });

Azure-Specific Configuration

Setting Value Description

ssl.rejectUnauthorized

true

Always use SSL for Azure

Default port 5432

Standard PostgreSQL port

PgBouncer port 6432

Use when PgBouncer enabled

Token scope https://ossrdbms-aad.database.windows.net/.default

Entra ID token scope

Token lifetime ~1 hour Refresh before expiry

Pool Sizing Guidelines

Workload max

idleTimeoutMillis

Light (dev/test) 5-10 30000

Medium (production) 20-30 30000

Heavy (high concurrency) 50-100 10000

Note: Azure PostgreSQL has connection limits based on SKU. Check your tier's max connections.

Best Practices

  • Always use connection pools for production applications

  • Use parameterized queries - Never concatenate user input

  • Always close connections - Use try/finally or connection pools

  • Enable SSL - Required for Azure (ssl: { rejectUnauthorized: true } )

  • Handle token refresh - Entra ID tokens expire after ~1 hour

  • Set connection timeouts - Avoid hanging on network issues

  • Use transactions - For multi-statement operations

  • Monitor pool metrics - Track pool.totalCount , pool.idleCount , pool.waitingCount

  • Graceful shutdown - Call pool.end() on application termination

  • Use TypeScript generics - Type your query results for safety

Key Types

import { Client, Pool, PoolClient, PoolConfig, QueryResult, QueryResultRow, DatabaseError, QueryConfig } from "pg";

Reference Links

Resource URL

node-postgres Docs https://node-postgres.com

npm Package https://www.npmjs.com/package/pg

GitHub Repository https://github.com/brianc/node-postgres

Azure PostgreSQL Docs https://learn.microsoft.com/azure/postgresql/flexible-server/

Passwordless Connection https://learn.microsoft.com/azure/postgresql/flexible-server/how-to-connect-with-managed-identity

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

github-issue-creator

No summary provided by upstream source.

Repository SourceNeeds Review
General

azure-observability

No summary provided by upstream source.

Repository SourceNeeds Review
General

azure-appconfiguration-java

No summary provided by upstream source.

Repository SourceNeeds Review
General

azure-kusto

No summary provided by upstream source.

Repository SourceNeeds Review