pg package) is the most widely used Postgres driver for Node.js. In this guide, you connect to ClickHouse Managed Postgres through a pg.Pool with full TLS certificate verification, create two tables, move money between accounts in a transaction, and serve the data from a small node:http server.
Prerequisites
- Node.js 20.6 or later (for the built-in
--env-fileflag). This guide was tested with Node.js 24.21.0 andpg8.23.1. - A ClickHouse Cloud account
psql, to create the database
Create a ClickHouse Managed Postgres service
In the ClickHouse Cloud console, click New service and select Postgres. The instance is ready in a few minutes. See the quickstart for a walkthrough.Get your connection details
Click Connect in the left sidebar of your service. With Directly selected, copy the connection URL from the url tab. Leave Use SSL off: the app passes the CA certificate in code instead of in the URL (see Configure the connection pool). This guide connects directly to Postgres on port5432. A long-lived Node.js server already keeps its own pool of connections through pg.Pool, so it doesn’t need a second pooler in front of Postgres. If you run many processes or serverless functions, use the bundled PgBouncer instead; see Use PgBouncer.
Next, download the CA certificate for your instance. Turn on Use SSL and click Download CA certificate, or go to Settings → CA Certificate → Download CA certificate. The certificate is unique to your instance.
Set up the project
Create a project and installpg:
.mjs extension, so they work whether your package.json uses CommonJS or ES modules.
Create the database
Move the downloaded<service-name>-ca-certificate.pem file into the project directory and rename it to ca-certificate.pem. Then create a database for the app:
.env file with the connection URL you copied, with the database name changed to guide_nodejs, and the path to the certificate:
.env
@, /, or #, percent-encode them in the URL.
Configure the connection pool
Createdb.mjs. It exports one pool that the rest of the app shares:
db.mjs
ssl: { ca } gives you the equivalent of sslmode=verify-full: pg checks that the server certificate is signed by your instance’s CA and that it matches the hostname. rejectUnauthorized defaults to true; don’t set it to false.
Create the schema
Createsetup.mjs. It creates the tables, inserts two accounts with parameterized queries, and reads them back:
setup.mjs
$1, $2, …) rather than concatenating them into the SQL string. pg sends them separately from the query text, which prevents SQL injection. JavaScript arrays map to Postgres arrays, as in the ANY($1) query.
Run it:
pg returns numeric (and bigint) values as strings so that no precision is lost. Convert them explicitly if you need numbers.
Run queries in a transaction
A transaction must run on a single connection, so check out a client withpool.connect, and always release it. Create transfer.mjs:
transfer.mjs
pool.query for the statements inside a transaction: each pool.query call can run on a different connection.
Serve the data over HTTP
Createserver.mjs. It serves the accounts and transfers as JSON on port 3107, accepts new transfers, and shuts down cleanly on SIGINT (Ctrl+C) or SIGTERM:
server.mjs
pool.end once, when the process shuts down, not after each request. It waits for checked-out clients to be released and then closes every connection.
Verify
Start the server:alice when it failed, but the ROLLBACK undid it. Open http://localhost:3107/accounts in your browser to see the balances. The response looks like this:
pg_stat_ssl:
DATABASE_CA_CERT at any other certificate, the connection fails with unable to verify the first certificate, which shows that the server certificate is actually checked.
Finally, stop the server with Ctrl+C. It prints SIGINT received, shutting down and then Pool closed.
You can also look at the data in the console. Open SQL console, expand the guide_nodejs database, and open the accounts table:
Use PgBouncer
To connect through the bundled PgBouncer, select via PgBouncer in the Connect modal and use port6432 in DATABASE_URL. No code changes are needed; the same CA certificate works.
PgBouncer runs in transaction pooling mode, so each transaction can run on a different Postgres connection. With pg:
- Parameterized queries, named prepared statements (
pool.query({ name, text, values })), and transactions that usepool.connectwork as shown in this guide. - SQL-level
PREPAREandEXECUTE, and session settings made withSET, don’t carry over between transactions. UseSET LOCALinside a transaction instead. pg_stat_sslreports the connection between PgBouncer and Postgres, so the TLS check query above returnsssl: false. Your app’s connection to PgBouncer is still verified: a wrong CA certificate fails in the same way.
Next steps
- Connection: connection strings, PgBouncer, and TLS
- Settings: change parameters such as
max_connections - Read replicas: send read-only queries to a replica
- High availability: standbys and failover