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:

FieldIncluded by defaultDescription
db_driverYesIdentifies Drizzle as the driver
fileYesSource file that triggered the query
func_nameWhen namedEnclosing function/method — edit-stable, omitted if anonymous
routeNoHTTP route (requires framework middleware)
methodNoHTTP request method (requires framework middleware)
Trace contextAutomaticOpenTelemetry trace/span IDs when a tracer is active
Custom fieldsNoAny 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 pathTagged?
Selects, inserts, updates, deletes through the patched dbYes
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 clientNo

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.