[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FD71WC2WPZ2D0J9T2TG4JR":3,"skill-same:01M6FD71WC2WPZ2D0J9T2TG4JR":49},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":22,"linkOnly":23,"markdownContent":24,"markdownBody":24,"frontMatter":25,"refName":26,"commitSha":27,"skillDirectory":28,"forksCount":29,"repositoryHtmlUrl":30,"repositoryHomepageUrl":31,"ownerHtmlUrl":32,"ownerBlogUrl":6,"ownerDescription":33,"updatedAt":34,"install":35,"resources":48},"01M6FD71WC2WPZ2D0J9T2TG4JR","azure-postgres-ts",null,"ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fmicrosoft\u002Fskills\u002Fazure-postgres-ts\u002FSKILL.md",[],[18,19,20,21],"azure","azure postgres ts","postgres","azure postgres","2026-11-10 10:12:30",false,"# 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\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 } from \"@azure\u002Fidentity\";\n\n\u002F\u002F For system-assigned managed identity\nconst credential = new DefaultAzureCredential();\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();\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",{},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fmicrosoft\u002Fskills\u002Fazure-postgres-ts",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":7,"skillName":5,"commands":36,"prompt":46,"zipAvailable":47},[37,40,43],{"tool":38,"command":39},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill azure-postgres-ts",{"tool":41,"command":42},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill azure-postgres-ts",{"tool":44,"command":45},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill azure-postgres-ts","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fmicrosoft\u002Fskills\u002Fazure-postgres-ts\u002FSKILL.md",true,[],[50,64,74,83,89,96],{"skillNo":51,"name":52,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":53,"categories":54,"keywords":60,"publishedAt":22},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[55],{"categoryNo":56,"categoryName":57,"taxonomyType":58,"taxonomyTypeDesc":59},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[61,62,63],"model","model hierarchy","model hierarchy skill",{"skillNo":65,"name":66,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":70,"publishedAt":22},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[69],{"categoryNo":56,"categoryName":57,"taxonomyType":58,"taxonomyTypeDesc":59},[71,72,73],"MCP","zoom","zoom mcp",{"skillNo":75,"name":76,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":79,"publishedAt":22},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[80,81,82,72],"sdk","zoom apps sdk","apps",{"skillNo":84,"name":85,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":86,"categories":87,"keywords":88,"publishedAt":22},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[85],{"skillNo":90,"name":91,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":92,"categories":93,"keywords":95,"publishedAt":22},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[94],{"categoryNo":56,"categoryName":57,"taxonomyType":58,"taxonomyTypeDesc":59},[91],{"skillNo":97,"name":98,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":99,"categories":100,"keywords":101,"publishedAt":22},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[98]]