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

I am an enthusiastic researcher and developer with a passion for using technology to innovate in business and education.
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:
where()Parameters Do Not Work: When executing a query likedb.select().from(table).where(eq(table.id, 'some-id')), Drizzle fails to filter the results. Instead, it returns all rows from the table, as if thewhereclause never existed..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 containundefined. This happens because all values from the row seem to be "crammed" into the first column's property.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
// 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
// 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
// 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
# 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.





