# Cloudflare D1 & Drizzle ORM Anomaly: where Parameters Ignored and .get() Returns undefined in Local Environments

**October 1, 2025** - Developers utilizing a modern stack with Hono, Drizzle ORM, and Cloudflare D1 may encounter a baffling anomaly during local development. A strange behavior has been observed where `select` queries with `where` parameters seem to ignore the provided filters, and the `.get()` method, which should return a single object, yields corrupted data instead.

This article will analyze the problem, trace its root cause based on the provided code, and offer practical solutions.

---

### \## The Anomaly: Observed Symptoms

This issue specifically arises when running an application locally with Drizzle ORM connected to a D1 database via a Cloudflare API proxy, as implemented in the provided `d1-proxy.ts` file. Here are the three main symptoms:

1. `where()` Parameters Do Not Work: When executing a query like [`db.select`](http://db.select)`().from(table).where(eq(`[`table.id`](http://table.id)`, 'some-id'))`, Drizzle fails to filter the results. Instead, it returns *all* rows from the table, as if the `where` clause never existed.
    
2. `.get()` Method Returns Corrupted Data: When attempting to fetch a single record using `.get()` (which is an optimization of `.limit(1)`), the result is highly unexpected. The returned object has only one column with the correct data, while the other columns contain `undefined`. This happens because all values from the row seem to be "crammed" into the first column's property.
    
3. **The Workaround:** `.limit(1)` Works, But...: Strangely, replacing `.get()` with `.limit(1)` successfully filters and fetches the data correctly. However, this method returns the data as an array containing a single object `[ { ... } ]`, rather than a plain object `{ ... }`. This forces the developer to always access the result via the first index (`result[0]`), which is slightly less ergonomic.
    

Let's look at a comparison:

TypeScript

```typescript
// FAILS: Returns all users, as if `where` was ignored
const user = await db.select().from(users).where(eq(users.id, userId));

// FAILS: Returns { id: '...', name: undefined, email: undefined }
const userObj = await db.select().from(users).where(eq(users.id, userId)).get();

// WORKS (Workaround): Returns [ { id: '...', name: '...', email: '...' } ]
const userArray = await db.select().from(users).where(eq(users.id, userId)).limit(1);
const userResult = userArray[0]; // Requires manual access to index 0
```

---

### \## Root Cause Analysis: The Proxy is the Key

After analyzing the `index.ts` and `d1-proxy.ts` files, it becomes clear that the problem lies not with Drizzle ORM or D1 itself, but with the **bridge** connecting them in the local environment: the **custom API proxy**.

Your `d1-proxy.ts` file creates a function that translates Drizzle calls into `fetch` requests to the Cloudflare D1 API. Let's break down its implementation:

TypeScript

```typescript
// FILE: src/api/db/d1-proxy.ts

// ... (other code)
export const createDbProxy = ({ accountId, databaseId, token }: DbProxyParams) => {
  const url = `...`;

  return async (sql: string, params: any[]): Promise<...> => {
    const response = await fetch(url, {
      // ... (headers)
      body: JSON.stringify({ sql, params }), // <-- Critical Point
    });
    // ... (response handling)
  };
};
```

The `drizzle-orm/sqlite-proxy` client is designed to work with a driver that can execute SQL. In this case, your proxy sends the raw SQL string along with a *separate parameter array* (`params`) to the D1 API.

Most likely, there is a mismatch in how the D1 API `/query` endpoint processes prepared statements sent via HTTP. It appears the endpoint executes the raw `sql` string without correctly binding the values from the `params` array. This is why the `where` clause containing placeholders (like `?` or `$1`) fails, and the query is executed as if there were no filter.

The strange behavior of `.get()` is likely a side effect of this issue. Since Drizzle expects a single row in response but receives many rows (because the filter failed), the data "hydration" process—mapping data from `rows` and `columns` into an object—gets confused, causing the values to stack up in the first column.

The `.limit(1)` method works because `LIMIT 1` is part of the `sql` string itself and is therefore executed correctly on the D1 side. It doesn't fix the parameter issue, but it effectively caps the incorrect result to just one row, which Drizzle can then process correctly.

---

### \## Solutions and Recommendations

Given that the root cause is the API proxy implementation, here are a few approaches to resolve it.

#### 1\. Stick with the `.limit(1)` Workaround

As you've already discovered, this is the quickest fix. For any query where you need a single record, replace `.get()` with `.limit(1)` and process the result as an array.

TypeScript

```typescript
// Change from:
// const page = await db.select().from(pages).where(eq(pages.id, id)).get();

// To:
const pages = await db.select().from(pages).where(eq(pages.id, id)).limit(1);
const page = pages.length > 0 ? pages[0] : undefined;

if (page) {
  // Do something with the 'page' object
}
```

Pros: Quick to implement without changing infrastructure.

Cons: Less ergonomic syntactically and requires manual checks for an empty array.

#### 2\. Use the Wrangler Local Development Environment (Highly Recommended)

The most reliable way to develop Cloudflare Workers applications is to use an environment that mimics production as closely as possible. Wrangler, the official CLI for Cloudflare, provides this functionality.

Instead of using an API proxy, run your development server with the `wrangler dev` command and add the `--remote` flag.

Bash

```typescript
# Replace the "dev" script in package.json or run directly
wrangler dev --remote --port 3000 server.ts
```

How does this solve the problem?

The --remote flag makes your local workerd server connect directly to your remote D1 database natively, without going through an HTTP API proxy. This way, Drizzle ORM (via drizzle-orm/d1) communicates with D1 using the actual bindings, just like in the production environment. All issues related to parameterization and the .get() method will disappear because the problematic proxy layer is no longer involved.

**Pros**:

* **Maximum Accuracy**: Your dev environment is nearly identical to production.
    
* **Better Performance**: Eliminates the overhead of HTTP API calls for every query.
    
* **No Anomalies**: Drizzle works exactly as intended.
    

---

### \## Conclusion

The anomaly you experienced is a classic example of the challenges that can arise when trying to mock or proxy cloud services in a local environment. While the API proxy is a clever idea to enable development without `wrangler`, it can introduce subtle incompatibility issues like this one.

For the smoothest and most error-free development experience with Cloudflare D1 and Drizzle ORM, **it is highly recommended to adopt the** `wrangler dev --remote` workflow. This ensures that the code you write and test locally will behave exactly the same when deployed to production.
