Azure PostgreSQL for TypeScript (node-postgres) Connect to Azure Database for PostgreSQL Flexible Server using the pg (node-postgres) package with support for password and Microsoft Entra ID (passwordless) authentication. Installation npm install pg @azure/identity npm install -D @types/pg Environment Variables
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., user@contoso.com
AZURE_POSTGRESQL_CLIENTID
< managed-identity-client-id
For user-assigned identity
- Authentication
- Option 1: Password Authentication
- 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
- (
- )
- ;
- Option 2: Microsoft Entra ID (Passwordless) - Recommended
- 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
- (
- )
- ;
- Core Workflows
- 1. Single Client Connection
- 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. Connection Pool (Recommended for 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
- }
- ,
- // 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. Parameterized Queries (Prevent SQL Injection)
- // ALWAYS use parameterized queries - never concatenate user input
- const
- userId
- =
- 123
- ;
- const
- =
- "user@example.com"
- ;
- // 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 *"
- ,
- [
- ,
- "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. 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. Transaction Helper Function
- 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 *"
- ,
- [
- "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. Typed Queries with TypeScript
- import
- {
- Pool
- ,
- QueryResult
- }
- from
- "pg"
- ;
- interface
- User
- {
- id
- :
- number
- ;
- :
- 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
- ,
- :
- string
- ,
- name
- :
- string
- )
- :
- Promise
- <
- User
- >
- {
- const
- result
- =
- await
- pool
- .
- query
- <
- User
- >
- (
- "INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *"
- ,
- [
- ,
- name
- ]
- )
- ;
- return
- result
- .
- rows
- [
- 0
- ]
- ;
- }
- Pool with Entra ID Token Refresh
- For long-running applications, tokens expire and need refresh:
- 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
- (
- )
- ;
- 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
- ;
- // Refresh 5 minutes before expiry
- return
- new
- Date
- (
- )
- >=
- new
- Date
- (
- this
- .
- tokenExpiry
- .
- getTime
- (
- )
- -
- 5
- *
- 60
- *
- 1000
- )
- ;
- }
- async
- getPool
- (
- )
- :
- Promise
- <
- Pool
- >
- {
- if
- (
- this
- .
- pool
- &&
- !
- this
- .
- isTokenExpired
- (
- )
- )
- {
- return
- this
- .
- pool
- ;
- }
- // Close existing pool if token expired
- 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
- ;
- }
- }
- }
- // Usage
- 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()"
- )
- ;
- Error Handling
- import
- {
- DatabaseError
- }
- from
- "pg"
- ;
- try
- {
- await
- pool
- .
- query
- (
- "INSERT INTO users (email) VALUES ($1)"
- ,
- [
- ]
- )
- ;
- }
- catch
- (
- error
- )
- {
- if
- (
- error
- instanceof
- DatabaseError
- )
- {
- switch
- (
- error
- .
- code
- )
- {
- case
- "23505"
- :
- // unique_violation
- console
- .
- error
- (
- "Duplicate entry:"
- ,
- error
- .
- detail
- )
- ;
- break
- ;
- case
- "23503"
- :
- // foreign_key_violation
- console
- .
- error
- (
- "Foreign key constraint failed:"
- ,
- error
- .
- detail
- )
- ;
- 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
- ;
- }
- Connection String Format
- // Alternative: Use connection string
- const
- pool
- =
- new
- Pool
- (
- {
- connectionString
- :
- `
- postgres://
- ${
- user
- }
- :
- ${
- password
- }
- @
- ${
- host
- }
- :
- ${
- port
- }
- /
- ${
- database
- }
- ?sslmode=require
- `
- }
- )
- ;
- // With SSL required (Azure)
- const
- connectionString
- =
- `
- postgres://user:password@server.postgres.database.azure.com:5432/mydb?sslmode=require
- `
- ;
- Pool Events
- 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-Specific Configuration
- Setting
- Value
- Description
- ssl.rejectUnauthorized
- true
- Always use SSL for Azure
- Default port
- 5432
- Standard PostgreSQL port
- PgBouncer port
- 6432
- Use when PgBouncer enabled
- Token scope
- https://ossrdbms-aad.database.windows.net/.default
- Entra ID token scope
- Token lifetime
- ~1 hour
- Refresh before expiry
- Pool Sizing Guidelines
- Workload
- max
- idleTimeoutMillis
- Light (dev/test)
- 5-10
- 30000
- Medium (production)
- 20-30
- 30000
- Heavy (high concurrency)
- 50-100
- 10000
- Note
- Azure PostgreSQL has connection limits based on SKU. Check your tier's max connections. Best Practices Always use connection pools for production applications Use parameterized queries - Never concatenate user input Always close connections - Use try/finally or connection pools Enable SSL - Required for Azure ( ssl: { rejectUnauthorized: true } ) Handle token refresh - Entra ID tokens expire after ~1 hour Set connection timeouts - Avoid hanging on network issues Use transactions - For multi-statement operations Monitor pool metrics - Track pool.totalCount , pool.idleCount , pool.waitingCount Graceful shutdown - Call pool.end() on application termination Use TypeScript generics - Type your query results for safety Key Types import { Client , Pool , PoolClient , PoolConfig , QueryResult , QueryResultRow , DatabaseError , QueryConfig } from "pg" ; Reference Links Resource URL node-postgres Docs https://node-postgres.com npm Package https://www.npmjs.com/package/pg GitHub Repository https://github.com/brianc/node-postgres Azure PostgreSQL Docs https://learn.microsoft.com/azure/postgresql/flexible-server/ Passwordless Connection https://learn.microsoft.com/azure/postgresql/flexible-server/how-to-connect-with-managed-identity When to Use This skill is applicable to execute the workflow or actions described in the overview.