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.
Prerequisites
Section titled “Prerequisites”- Node.js >= 24.8.0
- Drizzle ORM >= 0.35.0
- ESM project (
"type": "module"in yourpackage.json) — the package does not ship a CommonJS build
Installation
Section titled “Installation”sh npm install @query-doctor/sqlcommenter-drizzle
sh pnpm add @query-doctor/sqlcommenter-drizzle
Basic setup
Section titled “Basic setup”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 |
Framework integration
Section titled “Framework integration”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.
Express
Section titled “Express”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),);Fastify
Section titled “Fastify”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);Which query paths carry tags
Section titled “Which query paths carry tags”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.
Database support
Section titled “Database support”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.