azure-postgres-ts

I'm ready to translate text to French while preserving Markdown formatting. However, I notice your message appears to be incomplete - it only contains "---" and "|" characters. Please provide the complete text you'd like me to translate, and I'll: - Preserve all Markdown formatting (headings, lists, code blocks, tables, links) - Keep proper nouns, brands, technical identifiers, and commands in English - Return only the translation without preamble Please share the text you want translated.

npx skills add https://github.com/microsoft/skills --skill azure-postgres-ts

Azure PostgreSQL pour TypeScript (node-postgres)

Connectez-vous à Azure Database for PostgreSQL Flexible Server en utilisant le package pg (node-postgres) avec support pour l'authentification par mot de passe et Microsoft Entra ID (sans mot de passe).

Installation

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

Variables d'environnement

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

# Pour l'authentification par mot de passe
AZURE_POSTGRESQL_USER=<username>
AZURE_POSTGRESQL_PASSWORD=<password>

# Pour l'authentification Entra ID
AZURE_POSTGRESQL_USER=<entra-user>@<server>   # ex. user@contoso.com
AZURE_POSTGRESQL_CLIENTID=<managed-identity-client-id>  # Pour l'identité managée assignée par l'utilisateur
AZURE_TOKEN_CREDENTIALS=prod # Requis uniquement si DefaultAzureCredential est utilisé en production

Authentification

Option 1 : Authentification par mot de passe

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 }  // Requis pour Azure
});

await client.connect();

Option 2 : Microsoft Entra ID (Sans mot de passe) - Recommandé

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

// Dev local : DefaultAzureCredential. Production : définissez AZURE_TOKEN_CREDENTIALS=prod ou AZURE_TOKEN_CREDENTIALS=<specific_credential>
const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
// Ou utilisez une credential spécifique directement en production :
// Voir https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes
// const credential = new ManagedIdentityCredential();

// Pour l'identité managée assignée par l'utilisateur
// const credential = new DefaultAzureCredential({
//   managedIdentityClientId: process.env.AZURE_POSTGRESQL_CLIENTID
// });

// Acquérir un token d'accès pour 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,  // Utilisateur Entra ID
  password: tokenResponse.token,             // Token comme mot de passe
  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,
  ssl: { rejectUnauthorized: true }
});

await client.connect();

Flux de travail principaux

1. Connexion client unique

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();  // Toujours fermer la connexion
}

2. Pool de connexions (Recommandé pour la 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 },

  // Configuration du pool
  max: 20,                    // Connexions maximales dans le pool
  idleTimeoutMillis: 30000,   // Fermer les connexions inactives après 30s
  connectionTimeoutMillis: 10000  // Délai d'expiration pour les nouvelles connexions
});

// Requête utilisant le pool (acquiert et libère automatiquement la connexion)
const result = await pool.query("SELECT * FROM users WHERE id = $1", [userId]);

// Extraction explicite pour plusieurs requêtes
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();  // Retourner la connexion au pool
}

// Nettoyage à l'arrêt
await pool.end();

3. Requêtes paramétrées (Prévenir l'injection SQL)

// TOUJOURS utiliser des requêtes paramétrées - ne JAMAIS concaténer l'entrée utilisateur
const userId = 123;
const email = "user@example.com";

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

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

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

4. 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();
}

5. Fonction d'aide pour les transactions

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();
  }
}

// Utilisation
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];
});

6. Requêtes typées avec TypeScript

import { Pool, QueryResult } from "pg";

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

// Typer le résultat de la requête
const result: QueryResult<User> = await pool.query<User>(
  "SELECT * FROM users WHERE id = $1",
  [userId]
);

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

// Insertion sûre pour le type
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 avec actualisation du token Entra ID

Pour les applications longue durée, les tokens expirent et doivent être actualisés :

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({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
    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;
    // Actualiser 5 minutes avant l'expiration
    return new Date() >= new Date(this.tokenExpiry.getTime() - 5 * 60 * 1000);
  }

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

    // Fermer le pool existant si le token a expiré
    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;
    }
  }
}

// Utilisation
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()");

Gestion des erreurs

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("Entrée dupliquée :", error.detail);
        break;
      case "23503":  // foreign_key_violation
        console.error("Contrainte de clé étrangère échouée :", error.detail);
        break;
      case "42P01":  // undefined_table
        console.error("Table n'existe pas :", error.message);
        break;
      case "28P01":  // invalid_password
        console.error("Authentification échouée");
        break;
      case "57P03":  // cannot_connect_now (server starting)
        console.error("Serveur indisponible, réessayez plus tard");
        break;
      default:
        console.error(`Erreur PostgreSQL ${error.code} : ${error.message}`);
    }
  }
  throw error;
}

Format de chaîne de connexion

// Alternative : Utiliser une chaîne de connexion
const pool = new Pool({
  connectionString: `postgres://${user}:${password}@${host}:${port}/${database}?sslmode=require`
});

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

Événements du pool

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

pool.on("connect", (client) => {
  console.log("Nouveau client connecté au pool");
});

pool.on("acquire", (client) => {
  console.log("Client extrait du pool");
});

pool.on("release", (err, client) => {
  console.log("Client retourné au pool");
});

pool.on("remove", (client) => {
  console.log("Client supprimé du pool");
});

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

Configuration spécifique à Azure

Paramètre Valeur Description
ssl.rejectUnauthorized true Toujours utiliser SSL pour Azure
Port par défaut 5432 Port PostgreSQL standard
Port PgBouncer 6432 Utiliser quand PgBouncer est activé
Étendue du token https://ossrdbms-aad.database.windows.net/.default Étendue du token Entra ID
Durée de vie du token ~1 heure Actualiser avant l'expiration

Directives de dimensionnement du pool

Charge de travail max idleTimeoutMillis
Légère (dev/test) 5-10 30000
Moyenne (production) 20-30 30000
Élevée (concurrence élevée) 50-100 10000

Remarque : Azure PostgreSQL a des limites de connexion selon la SKU. Vérifiez les connexions maximales de votre tier.

Bonnes pratiques

  1. Toujours utiliser les pools de connexions pour les applications de production
  2. Utiliser des requêtes paramétrées - Ne JAMAIS concaténer l'entrée utilisateur
  3. Toujours fermer les connexions - Utiliser try/finally ou les pools de connexions
  4. Activer SSL - Requis pour Azure (ssl: { rejectUnauthorized: true })
  5. Gérer l'actualisation des tokens - Les tokens Entra ID expirent après ~1 heure
  6. Définir les délais d'expiration de connexion - Éviter les blocages sur les problèmes réseau
  7. Utiliser les transactions - Pour les opérations multi-instructions
  8. Surveiller les métriques du pool - Suivre pool.totalCount, pool.idleCount, pool.waitingCount
  9. Arrêt élégant - Appeler pool.end() à la fermeture de l'application
  10. Utiliser les génériques TypeScript - Typer les résultats de vos requêtes pour la sécurité

Types clés

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

Liens de référence

Ressource URL
Documentation node-postgres https://node-postgres.com
Package npm https://www.npmjs.com/package/pg
Référentiel GitHub https://github.com/brianc/node-postgres
Documentation Azure PostgreSQL https://learn.microsoft.com/azure/postgresql/flexible-server/
Connexion sans mot de passe https://learn.microsoft.com/azure/postgresql/flexible-server/how-to-connect-with-managed-identity