mcpField
A
azure-postgres-ts

azure-postgres-ts

github.com/sickn33データ・データベース48.7k71 回インストール今日更新
リポジトリ

azure-postgres-ts について

node-postgres を使って Azure Database for PostgreSQL Flexible Server に接続する TypeScript 向けスキルです。パスワードと Microsoft Entra ID 認証、接続プール、クエリ、トランザクションを扱います。

主な機能

  1. 01パスワードと Entra ID 認証
  2. 02単一クライアントとプール接続
  3. 03パラメーター化クエリとトランザクション
  4. 04型付きクエリとエラー処理

活用シーン

  1. 01TypeScript アプリから Azure PostgreSQL に接続
  2. 02Entra ID でパスワードレス接続
  3. 03プールまたはトランザクションでクエリを実行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill azure-postgres-ts

インストール後、Claude Code で/azure-postgres-tsを入力して読み込まれたか確認します。

詳細

TypeScript(node-postgres)向け Azure PostgreSQL

パスワード認証と Microsoft Entra ID(パスワードレス)認証に対応した pg(node-postgres)パッケージを使用して、Azure Database for PostgreSQL Flexible Server に接続します。

インストール

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

環境変数

# 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., [email protected]
AZURE_POSTGRESQL_CLIENTID=<managed-identity-client-id>  # For user-assigned identity

認証

オプション 1:パスワード認証
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();
オプション 2:Microsoft Entra ID(パスワードレス)- 推奨
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();

以下の例はそれぞれ独立した概略であり、インポート、スキーマ、および権限が設定された非本番環境のデータベースが必要です。パラメーターのバインドで保護できるのは値であり、識別子やテナント認可は保護されません。TypeScript のクエリージェネリックはコンパイル時のアサーションであり、実行時の結果検証ではありません。ロールバックに失敗した場合は、クライアントをプールに戻さず破棄し、元のエラーを保持してください。

主なワークフロー

1. 単一クライアント接続
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
}
2. 接続プール(本番環境に推奨)
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();
3. パラメーター化クエリー(SQL インジェクションを防止)
// ALWAYS use parameterized queries - never concatenate user input
const userId = 123;
const email = "[email protected]";

// 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]
);
4. トランザクション
const client = await pool.connect();

try {
  await client.query("BEGIN");
  
  const userResult = await client.query(
    "INSERT INTO users (email) VALUES ($1) RETURNING id",
    ["[email protected]"]
  );
  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();
}
5. トランザクションヘルパー関数
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 *",
    ["[email protected]"]
  );
  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];
});
6. 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];
}

Entra ID トークンの更新に対応したプール

新しい接続で最新のトークンを取得できるよう、インストール済みの node-postgres バージョンでサポートされている動的パスワードコールバックを使用してください。トークンの有効期限が切れるたびに共有プールを破棄して再作成しないでください。終了処理と同時に呼び出されたクライアント間で競合が起きる可能性があります。

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

const credential = new DefaultAzureCredential();
const azurePool = new Pool({
  host: process.env.AZURE_POSTGRESQL_HOST,
  database: process.env.AZURE_POSTGRESQL_DATABASE,
  user: process.env.AZURE_POSTGRESQL_USER,
  ssl: { rejectUnauthorized: true },
  password: async () => {
    const token = await credential.getToken("https://ossrdbms-aad.database.windows.net/.default");
    if (!token) throw new Error("Token acquisition failed");
    return token.token;
  },
});

最初に Entra のプリンシパルマッピングとサーバーの認証設定を確認してください。トークンは秘密情報です。決してログに記録しないでください。トークンの有効期限は発行元によって定められており、必ず 1 時間とは限りません。

エラー処理

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 (details redacted)");
        break;
      case "23503":  // foreign_key_violation
        console.error("Foreign key constraint failed (details redacted)");
        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;
}

接続文字列の形式

// Alternative: Use connection string
const pool = new Pool({
  connectionString: `postgres://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(database)}`,
  ssl: { rejectUnauthorized: true }
});

// With SSL required (Azure)
const connectionString = 
  `postgres://user:[email protected]:5432/mydb?sslmode=require`;

プールイベント

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 固有の設定

設定値説明
ssl.rejectUnauthorizedtrueAzure では必ず SSL を使用してください
デフォルトポート5432PostgreSQL の標準ポート
PgBouncer ポート6432PgBouncer が有効な場合に使用
トークンスコープhttps://ossrdbms-aad.database.windows.net/.defaultEntra ID トークンのスコープ
トークンの有効期間約 1 時間有効期限前に更新

プールサイズのガイドライン

ワークロードmaxidleTimeoutMillis
軽量(開発/テスト)5-1030000
中程度(本番環境)20-3030000
高負荷(高い同時実行性)50-10010000

注:Azure PostgreSQL の接続数上限は SKU によって異なります。お使いのプランの最大接続数を確認してください。

ベストプラクティス

  1. 本番アプリケーションでは必ず接続プールを使用する
  2. パラメーター化クエリーを使用する - ユーザー入力を連結しない
  3. 必ず接続を閉じる - try/finally または接続プールを使用する
  4. SSL を有効にする - Azure では必須です(ssl: { rejectUnauthorized: true })
  5. トークンの更新を処理する - Entra ID トークンは約 1 時間で有効期限が切れます
  6. 接続タイムアウトを設定する - ネットワークの問題によるハングを防ぐ
  7. トランザクションを使用する - 複数のステートメントを実行する場合
  8. プールのメトリクスを監視する - pool.totalCount、pool.idleCount、pool.waitingCount を追跡する
  9. グレースフルシャットダウン - アプリケーション終了時に pool.end() を呼び出す
  10. TypeScript ジェネリックを使用する - 安全性のため、クエリー結果に型を付ける

主な型

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

参考リンク

リソースURL
node-postgres ドキュメントhttps://node-postgres.com
npm パッケージhttps://www.npmjs.com/package/pg
GitHub リポジトリhttps://github.com/brianc/node-postgres
Azure PostgreSQL ドキュメントhttps://learn.microsoft.com/azure/postgresql/flexible-server/
パスワードレス接続https://learn.microsoft.com/azure/postgresql/flexible-server/how-to-connect-with-managed-identity

使用する場面

このスキルは、概要で説明されているワークフローやアクションを実行する際に適用できます。

制限事項

  • このスキルは、タスクが上記のスコープに明確に一致する場合にのみ使用してください。
  • 出力を、環境固有の検証、テスト、専門家によるレビューの代替として扱わないでください。
  • 必要な入力、権限、安全境界、成功基準が欠けている場合は、作業を中断して確認を求めてください。

コメント