TypeORM query profiling
Capture every SQL query executed by TypeORM and inspect them in the Database panel.
This tutorial shows how to add the TypeORM collector to profile SQL queries in a NestJS application that uses PostgreSQL via @nestjs/typeorm.
Prerequisites
@eleven-labs/nest-profilerinstalled and configured@nestjs/typeormandtypeorminstalled with a workingDataSource
Step 1 — Install the package
pnpm add @eleven-labs/nest-profiler-typeormStep 2 — Register the collector
Add TypeOrmCollectorModule after TypeOrmModule in your root module:
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeOrmCollectorModule } from '@eleven-labs/nest-profiler-typeorm';
@Module({
imports: [
TypeOrmModule.forRootAsync({ ... }),
ProfilerModule.forRoot({ isGlobal: true }),
TypeOrmCollectorModule.forRoot({
slowQueryThreshold: 100, // queries > 100ms are highlighted (default)
}),
],
})
export class AppModule {}No other configuration is needed — the collector injects the DataSource automatically via @InjectDataSource().
Step 3 — Instrument your services with spans
Use startSpan() to add meaningful labels to the Timeline panel alongside your TypeORM calls:
import { ProfilerService } from '@eleven-labs/nest-profiler';
@Injectable()
export class ProductsService {
constructor(
@InjectRepository(Product) private readonly repo: Repository<Product>,
private readonly profiler: ProfilerService,
) {}
async findAll(): Promise<Product[]> {
const stop = this.profiler.startSpan('db.products.findAll');
const result = await this.repo.find({ order: { createdAt: 'DESC' } });
stop();
return result;
}
}Step 4 — Test it
Start your application and make a request that triggers a database query:
curl -i http://localhost:3000/productsCopy the X-Debug-Token from the response headers, open /_profiler/{token}, and click the Database tab.
You will see:
- Each SQL query with its type badge (
SELECT,INSERT, …) - Duration per query with a bar chart indicator
- Slow queries highlighted in red
- Bound parameters
The Timeline panel shows the db.products.findAll span alongside other phases.
How it works
The collector patches dataSource.createQueryRunner() to wrap every QueryRunner.query() call with timing. The patch records entries directly into the CLS profile for the current request, then TypeOrmCollector.collect() reads and returns them.
This approach captures:
- All queries from TypeORM Repositories
- All queries from the EntityManager
- Raw queries via
dataSource.query()
Queries executed outside a request context (e.g., during module initialization) are silently ignored since there is no active CLS profile.
Synchronize in production
Never use synchronize: true in production. It auto-migrates the schema on startup and can cause
data loss. Use TypeORM migrations instead.