Skip to main content
Bun is a JavaScript and TypeScript runtime with a built-in Postgres client, Bun.sql, so you don’t need to install a driver. In this guide, you connect Bun to ClickHouse Managed Postgres over verified TLS, create a table, run queries and a transaction, and serve the rows over HTTP with Bun.serve.

Prerequisites

  • Bun 1.4 or later. This guide was tested with Bun 1.4.2 and Postgres 18.
  • A ClickHouse Cloud account.
  • psql, to create the database. You can also run the CREATE DATABASE statement in the SQL console.

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

Open your service and click Connect in the left sidebar. Keep Directly selected and turn on Use SSL. The URL now ends with sslmode=verify-full&sslrootcert=.... Click Download CA certificate to download the CA certificate for your instance. This guide connects directly on port 5432. A Bun.serve app is a long-running process, and Bun.sql keeps its own connection pool, so a server-side pooler isn’t needed. If you run many short-lived Bun processes, see Connect through PgBouncer.

Set up the project

Create a project:
Adding to an existing app?Skip mkdir and bun init -y, since Bun.sql is built into Bun and there’s nothing to install. In your project root, continue with Configure the connection, and also put NODE_EXTRA_CA_CERTS=./ca-certificate.pem in front of your app’s own start script so it can verify the server.

Configure the connection

Move the CA certificate you downloaded into the project and rename it to ca-certificate.pem:
Create a database for the app. Replace <PASSWORD> and the host with the values from the Connect modal:
Add the connection URL for the guide_bun database to .env, creating the file if it doesn’t exist. Bun loads .env automatically, and the sql export from bun reads DATABASE_URL:
.env
URL-encode the password if it contains special characters. Keep .env out of version control. Add these scripts to package.json, merging them into the scripts block if it already exists. They pass the CA certificate to Bun through NODE_EXTRA_CA_CERTS:
package.json
How Bun verifies the server certificate
  • sslmode=verify-full makes Bun.sql check both the certificate chain and the host name.
  • Don’t copy the sslrootcert parameter from the console URL. Bun.sql doesn’t read it. It forwards it to the server as a runtime setting, and the connection fails with unrecognized configuration parameter "sslrootcert".
  • NODE_EXTRA_CA_CERTS must be set in the environment when Bun starts. Bun doesn’t read it from .env, which is why it’s in the scripts.
  • In Bun 1.4.2, passing the downloaded certificate through the tls: { ca } option of SQL fails with certificate signature failure. Use NODE_EXTRA_CA_CERTS instead.

Create a table

Create setup.ts. It prints the TLS state of the session and creates a todos table:
setup.ts
Run it:
To confirm that the certificate is checked, run the file without the CA certificate. The connection is refused:

Query the database

Bun.sql uses tagged templates. Every interpolated value is sent as a bind parameter, so it’s safe to pass user input. Create queries.ts:
queries.ts
sql.begin runs the callback in a transaction. It commits when the callback returns and rolls back if it throws. Run it:

Serve the rows over HTTP

Create server.ts with a /todos endpoint that lists and creates rows:
server.ts
Start the server:
In another terminal, add a row:

Verify

Open http://localhost:3106/todos in your browser to see all rows. The response looks like this, formatted for readability:
In the console, open SQL console and expand guide_bun, then public, then todos to see the same rows:

Connect through PgBouncer

If you run many short-lived Bun processes, connect through the bundled PgBouncer instead. Select via PgBouncer in the Connect modal and change the port in DATABASE_URL to 6432. The CA certificate and sslmode=verify-full stay the same. PgBouncer runs in transaction pooling mode. Bun.sql uses named prepared statements by default, and in our tests they worked through PgBouncer with no errors. If you see prepared statement does not exist errors, create the client with prepare: false:
Pass prepare as an option, not in the URL. Like sslrootcert, a prepare=false URL parameter is forwarded to the server and rejected.

Next steps

Last modified on September 30, 2026