[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-a446b5f3-3e17-499f-886a-bf1218c2ce37":3,"$fFCTNbT3RRA1ECWDhjUmJ3mYdR7trkwNA678ndCdrZtQ":42},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":33},"a446b5f3-3e17-499f-886a-bf1218c2ce37","azure-postgres-ts","从Node.js\u002FTypeScript使用pg（node-postgres）包连接到Azure Database for PostgreSQL灵活服务器。","cat_coding_backend","mod_coding","sickn33,coding","---\nname: azure-postgres-ts\ndescription: Connect to Azure Database for PostgreSQL Flexible Server from Node.js\u002FTypeScript using the pg (node-postgres) package.\nrisk: unknown\nsource: community\ndate_added: '2026-02-27'\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\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\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,232,1997,"2026-05-16 13:07:22",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":25,"skillCount":32,"createdAt":26},"后端开发","backend","mdi-server","API、数据库、服务端架构",296,[34],{"id":35,"skillId":4,"version":36,"fileName":37,"fileSize":38,"filePath":39,"fileHash":40,"manifest":41,"createdAt":19},"8558a174-04e3-442d-9b84-26c683829142","1.0.0","azure-postgres-ts.zip",4197,"uploads\u002Fskills\u002Fa446b5f3-3e17-499f-886a-bf1218c2ce37\u002Fazure-postgres-ts.zip","3ef6fb214da80c64391008042c35f4179e585fe6b683ea2575bd820c15a1b5df","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":12963}]",{"code":43,"message":44,"data":45},200,"success",{"items":46,"stats":47,"page":50},[],{"averageRating":48,"totalRatings":48,"ratingCounts":49},0,[48,48,48,48,48],{"limit":51,"offset":48,"hasMore":52,"nextOffset":51,"ratedOnly":16},15,false]