NotesWhy slow queries hide in DI-heavy apps, and the tool I wrote about it

Profiling TypeORM queries in NestJS

The problem

NestJS makes it easy to stack repositories, services, and interceptors until no single place in your code issues a query, and no single place sees them all. The endpoint is slow, the code reads fine, and the forty queries hiding behind one innocent relations option stay invisible.

TypeORM's built-in answer is logging: true, which prints every statement to stdout. In development that is a firehose you learn to scroll past; in production you turn it off. Neither mode answers the questions that matter: which queries ran for this request, how long did each take, and which ones ran more times than they should have?

What you actually want to measure

  • Per-query duration, not just the slow-query threshold log.
  • Query count per request: the N+1 signal. Ninety near-identical selects behind one endpoint show up in a count and hide in a log stream.
  • Which entity and which call path produced the query, so the fix lands in the right repository.

TypeORM exposes the hook for all of this: a custom logger receives every query with its parameters and timing. The work is aggregation: grouping by statement shape, attaching counts and durations, and putting the result somewhere you look, instead of a log you don't.

The tool

I packaged that aggregation as nestjs-query-profiler, a NestJS module that hooks into TypeORM and reports database query performance per request. You register the module, and instead of a stdout firehose you get grouped, timed query profiles you can act on.

Setup and options are in the package README.

What it changed for me

I stopped profiling only after an endpoint got slow enough for someone to complain. Now I glance at query counts while building the feature, when the fix is still a one-line relations change instead of an incident.

← Back to notes