[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M69AW86ASHKV1DS0R2CBMV74":3,"skill-same:01M69AW86ASHKV1DS0R2CBMV74":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":53,"commitSha":54,"skillDirectory":55,"forksCount":56,"repositoryHtmlUrl":57,"repositoryHomepageUrl":58,"ownerHtmlUrl":59,"ownerBlogUrl":60,"ownerDescription":61,"updatedAt":62,"install":63,"resources":76},"01M69AW86ASHKV1DS0R2CBMV74","azure-postgres-ts",null,"Connect to Azure Database for PostgreSQL Flexible Server from Node.js\u002FTypeScript using the pg (node-postgres) package. Use for PostgreSQL queries, connection pooling, transactions, and Microsoft Entra ID (passwordless) authentication. Triggers: \"PostgreSQL\", \"postgres\", \"pg client\", \"node-postgres\", \"Azure PostgreSQL connection\", \"PostgreSQL TypeScript\", \"pg Pool\", \"passwordless postgres\".","microsoft\u002Fskills","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M69AJYYW4E1RMSYCQDJ4FJSH",3195,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fplugins\u002Fazure-sdk-typescript\u002Fskills\u002Fazure-postgres-ts\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,9],"azure","azure postgres ts","azure postgres","postgres","server","pooling","for","triggers","connection pooling","use","node","transactions","passwordless","typescript","package","postgresql","database","authentication","connect","entra id","2026-11-10 10:12:30",false,"---\nname: azure-postgres-ts\ndescription: |\n  Connect to Azure Database for PostgreSQL Flexible Server from Node.js\u002FTypeScript using the pg (node-postgres) package. Use for PostgreSQL queries, connection pooling, transactions, and Microsoft Entra ID (passwordless) authentication. Triggers: \"PostgreSQL\", \"postgres\", \"pg client\", \"node-postgres\", \"Azure PostgreSQL connection\", \"PostgreSQL TypeScript\", \"pg Pool\", \"passwordless postgres\".\nlicense: MIT\nmetadata:\n  author: Microsoft\n  version: \"1.0.0\"\n  package: pg\n---\n\n# Azure PostgreSQL for TypeScript (node-postgres)\n\nConnect to Azure Database for PostgreSQL Flexible Server using the `pg` (node-postgres) package with support for password and Microsoft Entra ID (passwordless) authentication.\n\n## Installation\n\n```bash\nnpm install pg @azure\u002Fidentity\nnpm install -D @types\u002Fpg\n```\n\n## Environment Variables\n\n```bash\n# Required\nAZURE_POSTGRESQL_HOST=\u003Cserver>.postgres.database.azure.com\nAZURE_POSTGRESQL_DATABASE=\u003Cdatabase>\nAZURE_POSTGRESQL_PORT=5432\n\n# For password authentication\nAZURE_POSTGRESQL_USER=\u003Cusername>\nAZURE_POSTGRESQL_PASSWORD=\u003Cpassword>\n\n# For Entra ID authentication\nAZURE_POSTGRESQL_USER=\u003Centra-user>@\u003Cserver>   # e.g., user@contoso.com\nAZURE_POSTGRESQL_CLIENTID=\u003Cmanaged-identity-client-id>  # For user-assigned identity\nAZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production\n```\n\n## Authentication\n\n### Option 1: Password Authentication\n\n```typescript\nimport { Client, Pool } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }  \u002F\u002F Required for Azure\n});\n\nawait client.connect();\n```\n\n### Option 2: Microsoft Entra ID (Passwordless) - Recommended\n\n```typescript\nimport { Client, Pool } from \"pg\";\nimport { DefaultAzureCredential, ManagedIdentityCredential } from \"@azure\u002Fidentity\";\n\n\u002F\u002F Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=\u003Cspecific_credential>\nconst credential = new DefaultAzureCredential({requiredEnvVars: [\"AZURE_TOKEN_CREDENTIALS\"]});\n\u002F\u002F Or use a specific credential directly in production:\n\u002F\u002F See https:\u002F\u002Flearn.microsoft.com\u002Fjavascript\u002Fapi\u002Foverview\u002Fazure\u002Fidentity-readme?view=azure-node-latest#credential-classes\n\u002F\u002F const credential = new ManagedIdentityCredential();\n\n\u002F\u002F For user-assigned managed identity\n\u002F\u002F const credential = new DefaultAzureCredential({\n\u002F\u002F   managedIdentityClientId: process.env.AZURE_POSTGRESQL_CLIENTID\n\u002F\u002F });\n\n\u002F\u002F Acquire access token for Azure PostgreSQL\nconst tokenResponse = await credential.getToken(\n  \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n);\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,  \u002F\u002F Entra ID user\n  password: tokenResponse.token,             \u002F\u002F Token as password\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\nawait client.connect();\n```\n\n## Core Workflows\n\n### 1. Single Client Connection\n\n```typescript\nimport { Client } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\ntry {\n  await client.connect();\n  \n  const result = await client.query(\"SELECT NOW() as current_time\");\n  console.log(result.rows[0].current_time);\n} finally {\n  await client.end();  \u002F\u002F Always close connection\n}\n```\n\n### 2. Connection Pool (Recommended for Production)\n\n```typescript\nimport { Pool } from \"pg\";\n\nconst pool = new Pool({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  \n  \u002F\u002F Pool configuration\n  max: 20,                    \u002F\u002F Maximum connections in pool\n  idleTimeoutMillis: 30000,   \u002F\u002F Close idle connections after 30s\n  connectionTimeoutMillis: 10000  \u002F\u002F Timeout for new connections\n});\n\n\u002F\u002F Query using pool (automatically acquires and releases connection)\nconst result = await pool.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n\n\u002F\u002F Explicit checkout for multiple queries\nconst client = await pool.connect();\ntry {\n  const res1 = await client.query(\"SELECT * FROM users\");\n  const res2 = await client.query(\"SELECT * FROM orders\");\n} finally {\n  client.release();  \u002F\u002F Return connection to pool\n}\n\n\u002F\u002F Cleanup on shutdown\nawait pool.end();\n```\n\n### 3. Parameterized Queries (Prevent SQL Injection)\n\n```typescript\n\u002F\u002F ALWAYS use parameterized queries - never concatenate user input\nconst userId = 123;\nconst email = \"user@example.com\";\n\n\u002F\u002F Single parameter\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\n\u002F\u002F Multiple parameters\nconst result = await pool.query(\n  \"INSERT INTO users (email, name, created_at) VALUES ($1, $2, NOW()) RETURNING *\",\n  [email, \"John Doe\"]\n);\n\n\u002F\u002F Array parameter\nconst ids = [1, 2, 3, 4, 5];\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = ANY($1::int[])\",\n  [ids]\n);\n```\n\n### 4. Transactions\n\n```typescript\nconst client = await pool.connect();\n\ntry {\n  await client.query(\"BEGIN\");\n  \n  const userResult = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING id\",\n    [\"user@example.com\"]\n  );\n  const userId = userResult.rows[0].id;\n  \n  await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2)\",\n    [userId, 99.99]\n  );\n  \n  await client.query(\"COMMIT\");\n} catch (error) {\n  await client.query(\"ROLLBACK\");\n  throw error;\n} finally {\n  client.release();\n}\n```\n\n### 5. Transaction Helper Function\n\n```typescript\nasync function withTransaction\u003CT>(\n  pool: Pool,\n  fn: (client: PoolClient) => Promise\u003CT>\n): Promise\u003CT> {\n  const client = await pool.connect();\n  try {\n    await client.query(\"BEGIN\");\n    const result = await fn(client);\n    await client.query(\"COMMIT\");\n    return result;\n  } catch (error) {\n    await client.query(\"ROLLBACK\");\n    throw error;\n  } finally {\n    client.release();\n  }\n}\n\n\u002F\u002F Usage\nconst order = await withTransaction(pool, async (client) => {\n  const user = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING *\",\n    [\"user@example.com\"]\n  );\n  const order = await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING *\",\n    [user.rows[0].id, 99.99]\n  );\n  return order.rows[0];\n});\n```\n\n### 6. Typed Queries with TypeScript\n\n```typescript\nimport { Pool, QueryResult } from \"pg\";\n\ninterface User {\n  id: number;\n  email: string;\n  name: string;\n  created_at: Date;\n}\n\n\u002F\u002F Type the query result\nconst result: QueryResult\u003CUser> = await pool.query\u003CUser>(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\nconst user: User | undefined = result.rows[0];\n\n\u002F\u002F Type-safe insert\nasync function createUser(\n  pool: Pool,\n  email: string,\n  name: string\n): Promise\u003CUser> {\n  const result = await pool.query\u003CUser>(\n    \"INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *\",\n    [email, name]\n  );\n  return result.rows[0];\n}\n```\n\n## Pool with Entra ID Token Refresh\n\nFor long-running applications, tokens expire and need refresh:\n\n```typescript\nimport { Pool, PoolConfig } from \"pg\";\nimport { DefaultAzureCredential, AccessToken } from \"@azure\u002Fidentity\";\n\nclass AzurePostgresPool {\n  private pool: Pool | null = null;\n  private credential: DefaultAzureCredential;\n  private tokenExpiry: Date | null = null;\n  private config: Omit\u003CPoolConfig, \"password\">;\n\n  constructor(config: Omit\u003CPoolConfig, \"password\">) {\n    this.credential = new DefaultAzureCredential({requiredEnvVars: [\"AZURE_TOKEN_CREDENTIALS\"]});\n    this.config = config;\n  }\n\n  private async getToken(): Promise\u003Cstring> {\n    const tokenResponse = await this.credential.getToken(\n      \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n    );\n    this.tokenExpiry = new Date(tokenResponse.expiresOnTimestamp);\n    return tokenResponse.token;\n  }\n\n  private isTokenExpired(): boolean {\n    if (!this.tokenExpiry) return true;\n    \u002F\u002F Refresh 5 minutes before expiry\n    return new Date() >= new Date(this.tokenExpiry.getTime() - 5 * 60 * 1000);\n  }\n\n  async getPool(): Promise\u003CPool> {\n    if (this.pool && !this.isTokenExpired()) {\n      return this.pool;\n    }\n\n    \u002F\u002F Close existing pool if token expired\n    if (this.pool) {\n      await this.pool.end();\n    }\n\n    const token = await this.getToken();\n    this.pool = new Pool({\n      ...this.config,\n      password: token\n    });\n\n    return this.pool;\n  }\n\n  async query\u003CT>(text: string, params?: any[]): Promise\u003CQueryResult\u003CT>> {\n    const pool = await this.getPool();\n    return pool.query\u003CT>(text, params);\n  }\n\n  async end(): Promise\u003Cvoid> {\n    if (this.pool) {\n      await this.pool.end();\n      this.pool = null;\n    }\n  }\n}\n\n\u002F\u002F Usage\nconst azurePool = new AzurePostgresPool({\n  host: process.env.AZURE_POSTGRESQL_HOST!,\n  database: process.env.AZURE_POSTGRESQL_DATABASE!,\n  user: process.env.AZURE_POSTGRESQL_USER!,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  max: 20\n});\n\nconst result = await azurePool.query(\"SELECT NOW()\");\n```\n\n## Error Handling\n\n```typescript\nimport { DatabaseError } from \"pg\";\n\ntry {\n  await pool.query(\"INSERT INTO users (email) VALUES ($1)\", [email]);\n} catch (error) {\n  if (error instanceof DatabaseError) {\n    switch (error.code) {\n      case \"23505\":  \u002F\u002F unique_violation\n        console.error(\"Duplicate entry:\", error.detail);\n        break;\n      case \"23503\":  \u002F\u002F foreign_key_violation\n        console.error(\"Foreign key constraint failed:\", error.detail);\n        break;\n      case \"42P01\":  \u002F\u002F undefined_table\n        console.error(\"Table does not exist:\", error.message);\n        break;\n      case \"28P01\":  \u002F\u002F invalid_password\n        console.error(\"Authentication failed\");\n        break;\n      case \"57P03\":  \u002F\u002F cannot_connect_now (server starting)\n        console.error(\"Server unavailable, retry later\");\n        break;\n      default:\n        console.error(`PostgreSQL error ${error.code}: ${error.message}`);\n    }\n  }\n  throw error;\n}\n```\n\n## Connection String Format\n\n```typescript\n\u002F\u002F Alternative: Use connection string\nconst pool = new Pool({\n  connectionString: `postgres:\u002F\u002F${user}:${password}@${host}:${port}\u002F${database}?sslmode=require`\n});\n\n\u002F\u002F With SSL required (Azure)\nconst connectionString = \n  `postgres:\u002F\u002Fuser:password@server.postgres.database.azure.com:5432\u002Fmydb?sslmode=require`;\n```\n\n## Pool Events\n\n```typescript\nconst pool = new Pool({ \u002F* config *\u002F });\n\npool.on(\"connect\", (client) => {\n  console.log(\"New client connected to pool\");\n});\n\npool.on(\"acquire\", (client) => {\n  console.log(\"Client checked out from pool\");\n});\n\npool.on(\"release\", (err, client) => {\n  console.log(\"Client returned to pool\");\n});\n\npool.on(\"remove\", (client) => {\n  console.log(\"Client removed from pool\");\n});\n\npool.on(\"error\", (err, client) => {\n  console.error(\"Unexpected pool error:\", err);\n});\n```\n\n## Azure-Specific Configuration\n\n| Setting | Value | Description |\n|---------|-------|-------------|\n| `ssl.rejectUnauthorized` | `true` | Always use SSL for Azure |\n| Default port | `5432` | Standard PostgreSQL port |\n| PgBouncer port | `6432` | Use when PgBouncer enabled |\n| Token scope | `https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default` | Entra ID token scope |\n| Token lifetime | ~1 hour | Refresh before expiry |\n\n## Pool Sizing Guidelines\n\n| Workload | `max` | `idleTimeoutMillis` |\n|----------|-------|---------------------|\n| Light (dev\u002Ftest) | 5-10 | 30000 |\n| Medium (production) | 20-30 | 30000 |\n| Heavy (high concurrency) | 50-100 | 10000 |\n\n> **Note**: Azure PostgreSQL has connection limits based on SKU. Check your tier's max connections.\n\n## Best Practices\n\n1. **Always use connection pools** for production applications\n2. **Use parameterized queries** - Never concatenate user input\n3. **Always close connections** - Use `try\u002Ffinally` or connection pools\n4. **Enable SSL** - Required for Azure (`ssl: { rejectUnauthorized: true }`)\n5. **Handle token refresh** - Entra ID tokens expire after ~1 hour\n6. **Set connection timeouts** - Avoid hanging on network issues\n7. **Use transactions** - For multi-statement operations\n8. **Monitor pool metrics** - Track `pool.totalCount`, `pool.idleCount`, `pool.waitingCount`\n9. **Graceful shutdown** - Call `pool.end()` on application termination\n10. **Use TypeScript generics** - Type your query results for safety\n\n## Key Types\n\n```typescript\nimport {\n  Client,\n  Pool,\n  PoolClient,\n  PoolConfig,\n  QueryResult,\n  QueryResultRow,\n  DatabaseError,\n  QueryConfig\n} from \"pg\";\n```\n\n## Reference Links\n\n| Resource | URL |\n|----------|-----|\n| node-postgres Docs | https:\u002F\u002Fnode-postgres.com |\n| npm Package | https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fpg |\n| GitHub Repository | https:\u002F\u002Fgithub.com\u002Fbrianc\u002Fnode-postgres |\n| Azure PostgreSQL Docs | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002F |\n| Passwordless Connection | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002Fhow-to-connect-with-managed-identity |\n","# Azure PostgreSQL for TypeScript (node-postgres)\n\nConnect to Azure Database for PostgreSQL Flexible Server using the `pg` (node-postgres) package with support for password and Microsoft Entra ID (passwordless) authentication.\n\n## Installation\n\n```bash\nnpm install pg @azure\u002Fidentity\nnpm install -D @types\u002Fpg\n```\n\n## Environment Variables\n\n```bash\n# Required\nAZURE_POSTGRESQL_HOST=\u003Cserver>.postgres.database.azure.com\nAZURE_POSTGRESQL_DATABASE=\u003Cdatabase>\nAZURE_POSTGRESQL_PORT=5432\n\n# For password authentication\nAZURE_POSTGRESQL_USER=\u003Cusername>\nAZURE_POSTGRESQL_PASSWORD=\u003Cpassword>\n\n# For Entra ID authentication\nAZURE_POSTGRESQL_USER=\u003Centra-user>@\u003Cserver>   # e.g., user@contoso.com\nAZURE_POSTGRESQL_CLIENTID=\u003Cmanaged-identity-client-id>  # For user-assigned identity\nAZURE_TOKEN_CREDENTIALS=prod # Required only if DefaultAzureCredential is used in production\n```\n\n## Authentication\n\n### Option 1: Password Authentication\n\n```typescript\nimport { Client, Pool } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }  \u002F\u002F Required for Azure\n});\n\nawait client.connect();\n```\n\n### Option 2: Microsoft Entra ID (Passwordless) - Recommended\n\n```typescript\nimport { Client, Pool } from \"pg\";\nimport { DefaultAzureCredential, ManagedIdentityCredential } from \"@azure\u002Fidentity\";\n\n\u002F\u002F Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=\u003Cspecific_credential>\nconst credential = new DefaultAzureCredential({requiredEnvVars: [\"AZURE_TOKEN_CREDENTIALS\"]});\n\u002F\u002F Or use a specific credential directly in production:\n\u002F\u002F See https:\u002F\u002Flearn.microsoft.com\u002Fjavascript\u002Fapi\u002Foverview\u002Fazure\u002Fidentity-readme?view=azure-node-latest#credential-classes\n\u002F\u002F const credential = new ManagedIdentityCredential();\n\n\u002F\u002F For user-assigned managed identity\n\u002F\u002F const credential = new DefaultAzureCredential({\n\u002F\u002F   managedIdentityClientId: process.env.AZURE_POSTGRESQL_CLIENTID\n\u002F\u002F });\n\n\u002F\u002F Acquire access token for Azure PostgreSQL\nconst tokenResponse = await credential.getToken(\n  \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n);\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,  \u002F\u002F Entra ID user\n  password: tokenResponse.token,             \u002F\u002F Token as password\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\nawait client.connect();\n```\n\n## Core Workflows\n\n### 1. Single Client Connection\n\n```typescript\nimport { Client } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\ntry {\n  await client.connect();\n  \n  const result = await client.query(\"SELECT NOW() as current_time\");\n  console.log(result.rows[0].current_time);\n} finally {\n  await client.end();  \u002F\u002F Always close connection\n}\n```\n\n### 2. Connection Pool (Recommended for Production)\n\n```typescript\nimport { Pool } from \"pg\";\n\nconst pool = new Pool({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  \n  \u002F\u002F Pool configuration\n  max: 20,                    \u002F\u002F Maximum connections in pool\n  idleTimeoutMillis: 30000,   \u002F\u002F Close idle connections after 30s\n  connectionTimeoutMillis: 10000  \u002F\u002F Timeout for new connections\n});\n\n\u002F\u002F Query using pool (automatically acquires and releases connection)\nconst result = await pool.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n\n\u002F\u002F Explicit checkout for multiple queries\nconst client = await pool.connect();\ntry {\n  const res1 = await client.query(\"SELECT * FROM users\");\n  const res2 = await client.query(\"SELECT * FROM orders\");\n} finally {\n  client.release();  \u002F\u002F Return connection to pool\n}\n\n\u002F\u002F Cleanup on shutdown\nawait pool.end();\n```\n\n### 3. Parameterized Queries (Prevent SQL Injection)\n\n```typescript\n\u002F\u002F ALWAYS use parameterized queries - never concatenate user input\nconst userId = 123;\nconst email = \"user@example.com\";\n\n\u002F\u002F Single parameter\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\n\u002F\u002F Multiple parameters\nconst result = await pool.query(\n  \"INSERT INTO users (email, name, created_at) VALUES ($1, $2, NOW()) RETURNING *\",\n  [email, \"John Doe\"]\n);\n\n\u002F\u002F Array parameter\nconst ids = [1, 2, 3, 4, 5];\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = ANY($1::int[])\",\n  [ids]\n);\n```\n\n### 4. Transactions\n\n```typescript\nconst client = await pool.connect();\n\ntry {\n  await client.query(\"BEGIN\");\n  \n  const userResult = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING id\",\n    [\"user@example.com\"]\n  );\n  const userId = userResult.rows[0].id;\n  \n  await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2)\",\n    [userId, 99.99]\n  );\n  \n  await client.query(\"COMMIT\");\n} catch (error) {\n  await client.query(\"ROLLBACK\");\n  throw error;\n} finally {\n  client.release();\n}\n```\n\n### 5. Transaction Helper Function\n\n```typescript\nasync function withTransaction\u003CT>(\n  pool: Pool,\n  fn: (client: PoolClient) => Promise\u003CT>\n): Promise\u003CT> {\n  const client = await pool.connect();\n  try {\n    await client.query(\"BEGIN\");\n    const result = await fn(client);\n    await client.query(\"COMMIT\");\n    return result;\n  } catch (error) {\n    await client.query(\"ROLLBACK\");\n    throw error;\n  } finally {\n    client.release();\n  }\n}\n\n\u002F\u002F Usage\nconst order = await withTransaction(pool, async (client) => {\n  const user = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING *\",\n    [\"user@example.com\"]\n  );\n  const order = await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING *\",\n    [user.rows[0].id, 99.99]\n  );\n  return order.rows[0];\n});\n```\n\n### 6. Typed Queries with TypeScript\n\n```typescript\nimport { Pool, QueryResult } from \"pg\";\n\ninterface User {\n  id: number;\n  email: string;\n  name: string;\n  created_at: Date;\n}\n\n\u002F\u002F Type the query result\nconst result: QueryResult\u003CUser> = await pool.query\u003CUser>(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\nconst user: User | undefined = result.rows[0];\n\n\u002F\u002F Type-safe insert\nasync function createUser(\n  pool: Pool,\n  email: string,\n  name: string\n): Promise\u003CUser> {\n  const result = await pool.query\u003CUser>(\n    \"INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *\",\n    [email, name]\n  );\n  return result.rows[0];\n}\n```\n\n## Pool with Entra ID Token Refresh\n\nFor long-running applications, tokens expire and need refresh:\n\n```typescript\nimport { Pool, PoolConfig } from \"pg\";\nimport { DefaultAzureCredential, AccessToken } from \"@azure\u002Fidentity\";\n\nclass AzurePostgresPool {\n  private pool: Pool | null = null;\n  private credential: DefaultAzureCredential;\n  private tokenExpiry: Date | null = null;\n  private config: Omit\u003CPoolConfig, \"password\">;\n\n  constructor(config: Omit\u003CPoolConfig, \"password\">) {\n    this.credential = new DefaultAzureCredential({requiredEnvVars: [\"AZURE_TOKEN_CREDENTIALS\"]});\n    this.config = config;\n  }\n\n  private async getToken(): Promise\u003Cstring> {\n    const tokenResponse = await this.credential.getToken(\n      \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n    );\n    this.tokenExpiry = new Date(tokenResponse.expiresOnTimestamp);\n    return tokenResponse.token;\n  }\n\n  private isTokenExpired(): boolean {\n    if (!this.tokenExpiry) return true;\n    \u002F\u002F Refresh 5 minutes before expiry\n    return new Date() >= new Date(this.tokenExpiry.getTime() - 5 * 60 * 1000);\n  }\n\n  async getPool(): Promise\u003CPool> {\n    if (this.pool && !this.isTokenExpired()) {\n      return this.pool;\n    }\n\n    \u002F\u002F Close existing pool if token expired\n    if (this.pool) {\n      await this.pool.end();\n    }\n\n    const token = await this.getToken();\n    this.pool = new Pool({\n      ...this.config,\n      password: token\n    });\n\n    return this.pool;\n  }\n\n  async query\u003CT>(text: string, params?: any[]): Promise\u003CQueryResult\u003CT>> {\n    const pool = await this.getPool();\n    return pool.query\u003CT>(text, params);\n  }\n\n  async end(): Promise\u003Cvoid> {\n    if (this.pool) {\n      await this.pool.end();\n      this.pool = null;\n    }\n  }\n}\n\n\u002F\u002F Usage\nconst azurePool = new AzurePostgresPool({\n  host: process.env.AZURE_POSTGRESQL_HOST!,\n  database: process.env.AZURE_POSTGRESQL_DATABASE!,\n  user: process.env.AZURE_POSTGRESQL_USER!,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  max: 20\n});\n\nconst result = await azurePool.query(\"SELECT NOW()\");\n```\n\n## Error Handling\n\n```typescript\nimport { DatabaseError } from \"pg\";\n\ntry {\n  await pool.query(\"INSERT INTO users (email) VALUES ($1)\", [email]);\n} catch (error) {\n  if (error instanceof DatabaseError) {\n    switch (error.code) {\n      case \"23505\":  \u002F\u002F unique_violation\n        console.error(\"Duplicate entry:\", error.detail);\n        break;\n      case \"23503\":  \u002F\u002F foreign_key_violation\n        console.error(\"Foreign key constraint failed:\", error.detail);\n        break;\n      case \"42P01\":  \u002F\u002F undefined_table\n        console.error(\"Table does not exist:\", error.message);\n        break;\n      case \"28P01\":  \u002F\u002F invalid_password\n        console.error(\"Authentication failed\");\n        break;\n      case \"57P03\":  \u002F\u002F cannot_connect_now (server starting)\n        console.error(\"Server unavailable, retry later\");\n        break;\n      default:\n        console.error(`PostgreSQL error ${error.code}: ${error.message}`);\n    }\n  }\n  throw error;\n}\n```\n\n## Connection String Format\n\n```typescript\n\u002F\u002F Alternative: Use connection string\nconst pool = new Pool({\n  connectionString: `postgres:\u002F\u002F${user}:${password}@${host}:${port}\u002F${database}?sslmode=require`\n});\n\n\u002F\u002F With SSL required (Azure)\nconst connectionString = \n  `postgres:\u002F\u002Fuser:password@server.postgres.database.azure.com:5432\u002Fmydb?sslmode=require`;\n```\n\n## Pool Events\n\n```typescript\nconst pool = new Pool({ \u002F* config *\u002F });\n\npool.on(\"connect\", (client) => {\n  console.log(\"New client connected to pool\");\n});\n\npool.on(\"acquire\", (client) => {\n  console.log(\"Client checked out from pool\");\n});\n\npool.on(\"release\", (err, client) => {\n  console.log(\"Client returned to pool\");\n});\n\npool.on(\"remove\", (client) => {\n  console.log(\"Client removed from pool\");\n});\n\npool.on(\"error\", (err, client) => {\n  console.error(\"Unexpected pool error:\", err);\n});\n```\n\n## Azure-Specific Configuration\n\n| Setting | Value | Description |\n|---------|-------|-------------|\n| `ssl.rejectUnauthorized` | `true` | Always use SSL for Azure |\n| Default port | `5432` | Standard PostgreSQL port |\n| PgBouncer port | `6432` | Use when PgBouncer enabled |\n| Token scope | `https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default` | Entra ID token scope |\n| Token lifetime | ~1 hour | Refresh before expiry |\n\n## Pool Sizing Guidelines\n\n| Workload | `max` | `idleTimeoutMillis` |\n|----------|-------|---------------------|\n| Light (dev\u002Ftest) | 5-10 | 30000 |\n| Medium (production) | 20-30 | 30000 |\n| Heavy (high concurrency) | 50-100 | 10000 |\n\n> **Note**: Azure PostgreSQL has connection limits based on SKU. Check your tier's max connections.\n\n## Best Practices\n\n1. **Always use connection pools** for production applications\n2. **Use parameterized queries** - Never concatenate user input\n3. **Always close connections** - Use `try\u002Ffinally` or connection pools\n4. **Enable SSL** - Required for Azure (`ssl: { rejectUnauthorized: true }`)\n5. **Handle token refresh** - Entra ID tokens expire after ~1 hour\n6. **Set connection timeouts** - Avoid hanging on network issues\n7. **Use transactions** - For multi-statement operations\n8. **Monitor pool metrics** - Track `pool.totalCount`, `pool.idleCount`, `pool.waitingCount`\n9. **Graceful shutdown** - Call `pool.end()` on application termination\n10. **Use TypeScript generics** - Type your query results for safety\n\n## Key Types\n\n```typescript\nimport {\n  Client,\n  Pool,\n  PoolClient,\n  PoolConfig,\n  QueryResult,\n  QueryResultRow,\n  DatabaseError,\n  QueryConfig\n} from \"pg\";\n```\n\n## Reference Links\n\n| Resource | URL |\n|----------|-----|\n| node-postgres Docs | https:\u002F\u002Fnode-postgres.com |\n| npm Package | https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fpg |\n| GitHub Repository | https:\u002F\u002Fgithub.com\u002Fbrianc\u002Fnode-postgres |\n| Azure PostgreSQL Docs | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002F |\n| Passwordless Connection | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002Fhow-to-connect-with-managed-identity |\n",{"name":5,"license":15,"metadata":49,"description":52},{"author":12,"package":50,"version":51},"pg","1.0.0","Connect to Azure Database for PostgreSQL Flexible Server from Node.js\u002FTypeScript using the pg (node-postgres) package. Use for PostgreSQL queries, connection pooling, transactions, and Microsoft Entra ID (passwordless) authentication. Triggers: \"PostgreSQL\", \"postgres\", \"pg client\", \"node-postgres\", \"Azure PostgreSQL connection\", \"PostgreSQL TypeScript\", \"pg Pool\", \"passwordless postgres\".\n","main","3f5b461b2314e01c400564051e92469114c5b68f",".github\u002Fplugins\u002Fazure-sdk-typescript\u002Fskills\u002Fazure-postgres-ts",365,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills","https:\u002F\u002Fmicrosoft.github.io\u002Fskills\u002F","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-12 02:02:58",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add microsoft\u002Fskills --skill azure-postgres-ts",{"tool":69,"command":70},"bunx","bunx skills add microsoft\u002Fskills --skill azure-postgres-ts",{"tool":72,"command":73},"pnpm","pnpm dlx skills add microsoft\u002Fskills --skill azure-postgres-ts","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fplugins\u002Fazure-sdk-typescript\u002Fskills\u002Fazure-postgres-ts\u002FSKILL.md",true,[],[78,103,139,166,185,223],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":87,"publishedAt":44},"01M69B4JCRQV5SPXAFCKXQRV0H","skill-creator","Guide for creating effective skills for AI coding agents working with Azure SDKs and Microsoft Foundry services. Use when creating new skills or updating existing skills.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fskill-creator\u002FSKILL.md",[84],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[88,89,90,9,91,92,93,24,94,95,96,33,97,98,99,30,100,101,102],"skill creator","creator","coding agents","foundry","microsoft foundry","ai coding agents","services","coding","guide","ai coding","with","sdks","Existing","when","new",{"skillNo":104,"name":105,"nameZh":6,"description":106,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":117,"publishedAt":44},"01M69B4FD57V1MYKRWAJJECTZ7","podcast-generation","Generate AI-powered podcast-style audio narratives using Azure OpenAI's GPT Realtime Mini model via WebSocket. Use when building text-to-speech features, audio narrative generation, podcast creation from content, or integrating with Azure OpenAI Realtime API for real audio output. Covers full-stack implementation from React frontend to Python FastAPI backend with WebSocket streaming.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fpodcast-generation\u002FSKILL.md",[109,112,113,116],{"categoryNo":110,"categoryName":111,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":114,"categoryName":115,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体",{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},[118,119,120,121,122,123,124,24,125,126,127,128,129,130,131,132,133,134,135,136,137,101,138],"podcast generation","podcast","generation","frontend","python","react","realtime","azure openai","generate","streaming","api","Audio","stack","text","model","fastapi","creation","python fastapi","narrative","features","fastapi backend",{"skillNo":140,"name":141,"nameZh":6,"description":142,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":146,"publishedAt":44},"01M69B4CYPM5SESY5ZWEFDXEQZ","microsoft-docs","Understand Microsoft technologies by querying official documentation. Use whenever the user asks how something works, wants tutorials, needs configuration options, limits, quotas, or best practices for any Microsoft technology (Azure, .NET, M365, Windows, Power Platform, etc.)—even if they don't mention \"docs.\" If the question is about understanding a concept rather than writing code, this is the right skill.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fmicrosoft-docs\u002FSKILL.md",[145],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},[147,9,148,149,150,151,152,153,154,155,156,24,157,158,159,33,160,66,161,162,163,164,165,30],"docs","microsoft docs","MCP","writing","cli","mcp server","windows","power platform","official","documentation","learn","best practices","question","power","back","technology","platform","learn mcp","concept",{"skillNo":167,"name":168,"nameZh":6,"description":169,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":170,"categories":171,"keywords":173,"publishedAt":44},"01M69B49C3YPD1ZJ3NC0HPCY58","mcp-builder","Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP), Node\u002FTypeScript (MCP SDK), or C#\u002F.NET (Microsoft MCP SDK).","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fmcp-builder\u002FSKILL.md",[172],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},[149,174,175,37,122,176,177,178,9,179,180,132,34,181,96,33,182,183,184,98,30,101],"mcp builder","builder","llms","sdk","model context protocol","fastmcp","Tools","context","integrate","mcp sdk","building mcp servers",{"skillNo":186,"name":187,"nameZh":6,"description":188,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":189,"categories":190,"keywords":203,"publishedAt":44},"01M69B467A7CDMFQ0T6Q0ZA12Q","kql","KQL language expertise for writing correct, efficient Kusto Query Language queries. Covers syntax gotchas, join patterns, dynamic types, datetime pitfalls, regex patterns, serialization, memory management, result-size discipline, and advanced functions (geo, vector, graph). USE THIS SKILL whenever writing, debugging, or reviewing KQL queries — even simple ones — because the gotchas section prevents the most common errors that waste tool calls and cause expensive retry cascades. Trigger on: KQL, Kusto, ADX, Azure Data Explorer, Fabric Real-Time Intelligence, EventHouse, Log Analytics, log analysis, data exploration, time series, anomaly detection, summarize, where clause, join, extend, project, let statement, parse operator, extract function, any mention of pipe-forward query syntax.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fkql\u002FSKILL.md",[191,194,195,200],{"categoryNo":192,"categoryName":193,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":196,"categoryName":197,"taxonomyType":198,"taxonomyTypeDesc":199},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",{"categoryNo":201,"categoryName":202,"taxonomyType":198,"taxonomyTypeDesc":199},"01JBSEEDCAT000000000000036","内容创作者",[187,150,204,205,206,24,207,208,209,210,211,212,213,214,215,216,217,218,219,30,220,221,222],"summarize","memory","analytics","vector","time series","tool","discipline","log","geo","exploration","pipe","time","project","regex","join","datetime","section","anomaly","parse",{"skillNo":224,"name":225,"nameZh":6,"description":226,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":227,"categories":228,"keywords":229,"publishedAt":44},"01M69B44HJQ7897EZ7NE7DWQX3","github-issue-creator","Convert raw notes, error logs, voice dictation, or screenshots into crisp GitHub-flavored markdown issue reports. Use when the user pastes bug info, error messages, or informal descriptions and wants a structured GitHub issue. Supports images\u002FGIFs for visual evidence.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fskills\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fgithub-issue-creator\u002FSKILL.md",[],[230,231,232,233,89,234,235,236,237,238,239,240,33,241,242,243,244,245,246,30,101],"github","github issue creator","github issue","issue","issue creator","screenshots","notes","logs","error messages","messages","convert","evidence","reports","images","info","visual","error"]