Skip to content

SQLCommenter for Drizzle

@query-doctor/sqlcommenter-drizzle patches Drizzle ORM so every SQL query it executes carries a structured comment with contextual metadata — the source file, HTTP route, request method, and OpenTelemetry trace context. Query Doctor reads these comments to connect slow or failing queries back to the code and request that produced them.

  • Node.js >= 24.8.0
  • Drizzle ORM >= 0.35.0
  • ESM project ("type": "module" in your package.json) — the package does not ship a CommonJS build

sh npm install @query-doctor/sqlcommenter-drizzle

Wrap your Drizzle instance with patchDrizzle:

import { drizzle } from "drizzle-orm/node-postgres";
import { patchDrizzle } from "@query-doctor/sqlcommenter-drizzle";
const db = patchDrizzle(drizzle(process.env.DATABASE_URL));

Every query executed through db now includes a SQL comment with:

Field Included by default Description
db_driver Yes Identifies Drizzle as the driver
file Yes Source file that triggered the query
func_name When named Enclosing function/method — edit-stable, omitted if anonymous
route No HTTP route (requires framework middleware)
method No HTTP request method (requires framework middleware)
Trace context Automatic OpenTelemetry trace/span IDs when a tracer is active
Custom fields No Any arbitrary key/value metadata you provide

To capture route and method, open a request-scoped context with withRequestContext from the /http export. It takes the context object and the framework’s next/done callback, and runs it within an AsyncLocalStorage scope so every query issued during the request inherits the context. You can include any arbitrary key/value metadata alongside route and method.

import express from "express";
import { withRequestContext } from "@query-doctor/sqlcommenter-drizzle/http";
const app = express();
app.use((req, res, next) => {
withRequestContext({ route: req.route?.path, method: req.method }, next);
});
import { Hono } from "hono";
import { routePath } from "hono/route";
import { withRequestContext } from "@query-doctor/sqlcommenter-drizzle/http";
const app = new Hono();
app.use((c, next) =>
withRequestContext({ route: routePath(c), method: c.req.method }, next),
);

The simplest way is the first-party plugin, which registers a global onRequest hook correctly:

import Fastify from "fastify";
import { sqlcommenterFastify } from "@query-doctor/sqlcommenter-drizzle/fastify";
const fastify = Fastify();
// Register before any plugin whose hooks issue queries (e.g. auth), so the
// context is already open when those hooks run.
await fastify.register(sqlcommenterFastify);

It tags queries from the whole request lifecycle — including queries issued in other plugins’ onRequest/preHandler hooks — not just the route handler.

If you wire it by hand instead, register the hook globally with fastify-plugin (a plain register() encapsulates the hook and it silently tags nothing), hook onRequest, and register it before any query-issuing plugin:

import fp from "fastify-plugin";
import { withRequestContext } from "@query-doctor/sqlcommenter-drizzle/http";
const sqlcommenter = fp((app, _opts, done) => {
app.addHook("onRequest", (request, _reply, next) => {
withRequestContext(
{
route: request.routeOptions?.url ?? request.url,
method: request.method,
},
next,
);
});
done();
});
await fastify.register(sqlcommenter);

Tags are applied by the patched instance, so a query is tagged only if it runs through the db that patchDrizzle returns:

Query path Tagged?
Selects, inserts, updates, deletes through the patched db Yes
Queries inside db.transaction(...) Yes, since 0.6.0
A Drizzle instance you didn’t wrap with patchDrizzle (e.g. a standalone migration runner) No
Raw queries issued through the underlying postgres / pg client No

Transaction queries were silently untagged before 0.6.0 — they ran without a comment and carried no source tag. Upgrade to @query-doctor/sqlcommenter-drizzle >= 0.6.0 to tag them. For paths that stay uncovered, wrap the relevant Drizzle instance with patchDrizzle too, or accept that those queries won’t carry a source tag.

Tagging a query is necessary but not sufficient for it to show up in Query Doctor — a run still has to record the query text with the comment intact. See the CI Quickstart.

The package is primarily tested on PostgreSQL but should work with any database Drizzle supports, since the annotation is applied at the query string level.