drizzle-kit is its companion CLI for generating and running migrations. In this guide, you define a todos table in TypeScript, create it with a drizzle-kit migration, and serve create, read, update, and delete queries from a small Node.js HTTP server. Every connection uses TLS with full certificate verification.
Prerequisites
- Node.js 20 or later
- A ClickHouse Cloud account
psql, to create the database. You can also run theCREATE DATABASEstatement 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
Click Connect in the left sidebar of your service. The modal shows your username, password, server, and port, and it has a Directly / via PgBouncer toggle. This guide uses two connection strings:- Direct (port
5432) fordrizzle-kit. Migrations run DDL, can run for a long time, and may depend on session state such asSET lock_timeout, so they should talk to Postgres directly. - PgBouncer (port
6432) for the application. The bundled PgBouncer runs in transaction pooling mode and lets many app instances, including serverless functions, share a small number of Postgres connections.
sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem, and the port is 5432. Select via PgBouncer to see the pooled connection details: the port changes to 6432, and the URL always includes sslmode=verify-full.
In the modal, click Download CA certificate. You can also download it from Settings → CA Certificate. The certificate is unique to your instance, so the driver can use it to verify that it’s talking to your server.
Set up the project
Create a project and install Drizzle ORM, thenode-postgres driver (pg), and drizzle-kit:
Configure the connection
Move the CA certificate you downloaded into the project directory and rename it toca-certificate.pem.
Create a database for the app. Replace <PASSWORD> and the host with the values from the Connect modal:
.env file in the project root, or to your existing .env. They differ only in the port, and both point at the guide_drizzle database:
.env
node-postgres reads sslmode=verify-full and sslrootcert from the URL. It loads the CA file and checks both the certificate chain and the hostname, so you don’t need any extra TLS code. The sslrootcert path is resolved relative to the directory you run commands from. Keep .env out of version control.
If the CA is missing or wrong, the connection fails with
unable to verify the first certificate. If the hostname doesn’t match the certificate, it fails with ERR_TLS_CERT_ALTNAME_INVALID. If your password contains special characters such as @, /, or #, URL-encode them.Define the schema
Createsrc/db/schema.ts:
src/db/schema.ts
drizzle.config.ts in the project root. It points drizzle-kit at the schema and at the direct connection:
drizzle.config.ts
Run migrations
Generate a SQL migration from the schema:drizzle/0000_init.sql contains the CREATE TABLE statement. Review it and commit the drizzle folder with your code. Then apply it:
drizzle-kit migrate records applied migrations in the drizzle.__drizzle_migrations table and only runs new ones on later calls. Each time you change schema.ts, run generate and then migrate again.
drizzle-kit push applies schema changes without migration files. It’s handy for prototyping, but generate and migrate give you reviewable SQL files and a history for production.
Query the database
Createsrc/db/index.ts. It creates the Drizzle client over a node-postgres pool using the PgBouncer connection:
src/db/index.ts
src/server.ts, a small HTTP server that runs one Drizzle query per route. In an existing app, you can call the same db queries from your own route handlers instead:
src/server.ts
No driver settings are needed for PgBouncer. Drizzle’s queries use unnamed statements by default, and the bundled PgBouncer also supports named prepared statements, such as those created with Drizzle’s
.prepare, in transaction pooling mode. Session-level features such as LISTEN and session advisory locks don’t work through PgBouncer. See the FAQ.Run and verify
Start the server:PATCH request returns the updated row:
http://localhost:3101/todos in your browser to list the remaining todos. The response looks like this:
guide_drizzle and then public, and click the todos table. The drizzle schema holds the migration history table. The table contains the remaining row:
Next steps
- Connection: connection strings, PgBouncer, and TLS
- Settings: Postgres and PgBouncer parameters, such as
max_connections - Read replicas: scale reads with Drizzle’s
withReplicas - Security: IP access lists and private networking