---
sidebar_position: 5
title: Connect from Cloudflare Workers
description: Query MotherDuck from Cloudflare Workers using the Postgres wire protocol
feature_stage: preview
---

Cloudflare Workers do not support native DuckDB bindings, but they can connect to MotherDuck through the [Postgres endpoint](/key-tasks/authenticating-and-connecting-to-motherduck/postgres-endpoint) using the [`pg`](https://www.npmjs.com/package/pg) npm package. This gives you a thin-client path to query MotherDuck from edge functions without any DuckDB dependencies.

This guide walks through building a Worker that queries NYC taxi data from MotherDuck's built-in `sample_data` database. The full source code is available in the [motherduck-examples](https://github.com/motherduckdb/motherduck-examples/tree/main/cloudflare-workers) repository.

## Prerequisites

- [Node.js](https://nodejs.org/) v18+
- A [Cloudflare account](https://dash.cloudflare.com/sign-up)
- A [MotherDuck account](https://motherduck.com/) and [access token](/key-tasks/authenticating-and-connecting-to-motherduck/authenticating-to-motherduck)

## Project setup

Create a new directory and install dependencies:

```bash
mkdir motherduck-worker && cd motherduck-worker
npm init -y
npm install pg@^8.16.3
npm install --save-dev wrangler @types/pg
```

### Configure wrangler.toml

```toml
name = "motherduck-taxi-stats"
main = "src/index.ts"
compatibility_date = "2026-04-02"
compatibility_flags = ["nodejs_compat"]

[vars]
MOTHERDUCK_HOST = "pg.us-east-1-aws.motherduck.com"
MOTHERDUCK_DB = "sample_data"
```

The `nodejs_compat` flag is required — it enables the `node:net` module that the `pg` package uses for TCP connections. Use a `compatibility_date` on or after `2024-09-23`; in practice, set it to today's date when you create the project.

Generate the Worker binding types after you save `wrangler.toml`:

```bash
npx wrangler types
```

### Store your token as a secret

```bash
npx wrangler secret put MOTHERDUCK_TOKEN
```

This prompts you to paste your MotherDuck token. It's stored encrypted and injected as an environment variable at runtime — it never appears in your source code or `wrangler.toml`.

For local development, create a `.dev.vars` file (add this to `.gitignore`):

```text
MOTHERDUCK_TOKEN="your_token_here"
```

## Write the Worker

Create `src/index.ts`. We'll build this in two parts: first the connection and routing, then the route handlers.

### Connect and route requests

```typescript
import { Client } from "pg";

interface Env {
  MOTHERDUCK_HOST: string;
  MOTHERDUCK_DB: string;
  MOTHERDUCK_TOKEN: string;
}

function createClient(env: Env): Client {
  return new Client({
    connectionString:
      `postgresql://user:${env.MOTHERDUCK_TOKEN}@${env.MOTHERDUCK_HOST}:5432/${env.MOTHERDUCK_DB}?sslmode=require`,
  });
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/stats") {
      return handleStats(env, url);
    }

    return handleDefault(env);
  },
};
```

The connection string is assembled from the environment variables defined in `wrangler.toml` and the secret token. The `?sslmode=require` parameter tells `pg` to open a TLS connection, and the Workers runtime performs certificate verification.

The `fetch` handler routes first and opens a database connection only inside the route handlers. That keeps validation failures on `/stats` returning `400` instead of depending on database connectivity.

### Handle route logic

Add the two handler functions to the same file. The `/stats` route accepts date range parameters and returns aggregated fare data. It validates inputs before querying and uses parameterized queries (`$1`, `$2`) to prevent SQL injection — never interpolate user input directly into SQL strings.

```typescript
async function handleStats(env: Env, url: URL): Promise<Response> {
  const startDate = url.searchParams.get("start");
  const endDate = url.searchParams.get("end");

  if (!startDate || !endDate) {
    return Response.json(
      { error: "Both 'start' and 'end' query parameters are required. Use YYYY-MM-DD format." },
      { status: 400 }
    );
  }

  const datePattern = /^\d{4}-\d{2}-\d{2}$/;
  if (!datePattern.test(startDate) || !datePattern.test(endDate)) {
    return Response.json(
      { error: "Invalid date format. Use YYYY-MM-DD." },
      { status: 400 }
    );
  }

  const client = createClient(env);

  try {
    await client.connect();

    const result = await client.query(
      `SELECT
        sum(passenger_count)::INTEGER AS total_passengers,
        round(sum(fare_amount), 2) AS total_fare
      FROM nyc.taxi
      WHERE tpep_pickup_datetime >= $1
        AND tpep_pickup_datetime < $2`,
      [`${startDate} 00:00:00`, `${endDate} 00:00:00`]
    );

    return Response.json({
      start: startDate,
      end: endDate,
      ...result.rows[0],
    });
  } finally {
    await client.end();
  }
}
```

The default route returns a sample of recent taxi trips — no user input needed:

```typescript
async function handleDefault(env: Env): Promise<Response> {
  const client = createClient(env);

  try {
    await client.connect();

    const result = await client.query(
      `SELECT
        tpep_pickup_datetime AS pickup,
        tpep_dropoff_datetime AS dropoff,
        passenger_count,
        trip_distance,
        fare_amount,
        tip_amount,
        total_amount
      FROM nyc.taxi
      ORDER BY tpep_pickup_datetime DESC
      LIMIT 20`
    );

    return Response.json(result.rows);
  } finally {
    await client.end();
  }
}
```

## Test locally

```bash
npx wrangler dev
```

Then open `http://localhost:8787/` or try the stats endpoint with a date range:

```text
http://localhost:8787/stats?start=2022-11-01&end=2022-12-01
```

If `wrangler dev` starts successfully but direct Postgres queries fail locally with `Connection terminated`, switch to the Hyperdrive setup below and use a `localConnectionString` for local testing, or run `npx wrangler dev --remote` to exercise the Cloudflare runtime directly.

## Deploy

```bash
npx wrangler deploy
```

## Using Hyperdrive for connection pooling

For production workloads, [Cloudflare Hyperdrive](https://developers.cloudflare.com/hyperdrive/) provides built-in connection pooling. This reduces latency by reusing connections across Worker invocations instead of opening a new connection per request.

### 1. create a Hyperdrive configuration

```bash
npx wrangler hyperdrive create motherduck-db \
  --connection-string="postgresql://user:$MOTHERDUCK_TOKEN@pg.us-east-1-aws.motherduck.com:5432/sample_data?sslmode=require"
```

### 2. update wrangler.toml

```toml
name = "motherduck-taxi-stats"
main = "src/index.ts"
compatibility_date = "2026-04-02"
compatibility_flags = ["nodejs_compat"]

[[hyperdrive]]
binding = "MD_HYPERDRIVE"
id = "<your-hyperdrive-config-id>"
```

### 3. update the connection code

Replace the connection string construction with:

```typescript
const client = new Client({
  connectionString: env.MD_HYPERDRIVE.connectionString,
});
```

Hyperdrive handles connection pooling and credential injection automatically.

For local development with Hyperdrive, configure a direct connection string for `wrangler dev`:

```bash
export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_MD_HYPERDRIVE="postgresql://user:$MOTHERDUCK_TOKEN@pg.us-east-1-aws.motherduck.com:5432/sample_data?sslmode=require"
npx wrangler dev
```

## SSL notes

Cloudflare Workers use `pg-cloudflare` for socket connections, which delegates TLS to the Workers runtime through `cloudflare:sockets`. The runtime encrypts the connection and verifies the server certificate against Cloudflare's trust store, but those verification settings are not exposed through the `pg` client. In this environment, application code uses the runtime-managed TLS configuration rather than supplying `rejectUnauthorized`, custom CA certificates, or `sslmode=verify-full`.

Use `?sslmode=require` in the connection string. This tells `pg` to initiate TLS using STARTTLS, and the Workers runtime handles the actual certificate verification at the socket level.

For standard Node.js environments where you can configure certificate verification directly, see [Connect from Node.js](/key-tasks/authenticating-and-connecting-to-motherduck/postgres-endpoint/nodejs).
