NestJS Profiler
Packages

nest-profiler-http

Record outgoing HTTP client calls in the HTTP Client panel.

@eleven-labs/nest-profiler-http

@eleven-labs/nest-profiler-http captures outgoing HTTP requests and displays them in a dedicated HTTP Client panel. It is client-agnostic: it owns the HttpRequestEntry contract, the collector, the HttpProfilerRecorder and an HttpInstrumentation interface, and never depends on any HTTP-client library. It ships two opt-in, subpath-isolated adapters — axios and fetch — that both capture request and response bodies safely, and you pick exactly which client(s) to instrument. Nothing is patched unless you select it, and you can bring your own client the same way.

HTTP Client panel — outgoing requests with method, URL, status and duration

Installation

pnpm add @eleven-labs/nest-profiler-http@alpha
# only if you select the axios adapter — your app already owns these:
pnpm add @nestjs/axios axios

There is no stable release yet — install every @eleven-labs/nest-profiler* package with the @alpha dist-tag (@latest resolves to nothing).

Optional peer dependency: axios ^1.0.0 (type-only, used by the /axios adapter). fetch is a Node ≥ 22 built-in and needs no dependency. This package never imports @nestjs/axios — that is your application's dependency.

Selecting clients

Import each adapter from its own subpath and list it in instrumentations. Nothing is instrumented unless it appears in the list.

app.module.ts
import { ConditionalModule } from '@nestjs/config';
import { HttpCollectorModule } from '@eleven-labs/nest-profiler-http';
import { AxiosInstrumentation } from '@eleven-labs/nest-profiler-http/axios';
import { FetchInstrumentation } from '@eleven-labs/nest-profiler-http/fetch';

const isProfilerEnabled = (env: NodeJS.ProcessEnv) => env['PROFILER_ENABLED'] === 'true';

@Module({
  imports: [
    ConditionalModule.registerWhen(
      HttpCollectorModule.forRoot({
        instrumentations: [AxiosInstrumentation, FetchInstrumentation],
        captureResponseBody: true,
      }),
      isProfilerEnabled,
    ),
  ],
})
export class AppModule {}

Each adapter lives on its own subpath (/axios, /fetch), so importing one never loads another's dependency. The root barrel exports only the client-agnostic API.

Enabling / disabling — gate the collector with ConditionalModule.registerWhen(..., isProfilerEnabled) as shown, so it loads only when PROFILER_ENABLED is on. Wire the core ProfilerModule once at the root — the recommended setup bundles the root-level profiler modules into a single ProfilingModule behind a ConditionalModule gate (see Enabling and disabling the profiler and the example app). A top-level enabled option is also supported as an alternative.

How each adapter finds requests

  • AxiosInstrumentation (/axios) — auto-discovers every axios instance in the DI container via DiscoveryService: @nestjs/axios HttpService (including each per-feature HttpModule / HttpModule.register(), which build distinct instances) and bare axios instances provided directly. No axiosRef wiring, no @nestjs/axios import. Just inject HttpService in your services as usual — requests are captured automatically. Axios instances created outside DI (a bare axios.create() held in a private field, a third-party library's internal client) aren't discoverable — record those with a custom instrumentation (below).
  • FetchInstrumentation (/fetch) — patches globalThis.fetch once. A single global hook covers every caller.

Other clients (got, undici, superagent…)? There is no node:http catch-all: instrument them with a small custom HttpInstrumentation using the client's own hooks (see Bring your own HTTP client). Going through the client's native API captures full request and response bodies safely — which a generic node:http hook cannot do for response bodies.

Bring your own HTTP client

For an ad-hoc call, inject HttpProfilerRecorder and call capture() — it applies your capture options (headers/body) and masks sensitive headers, so a custom client shows the same detail in the panel:

import { HttpProfilerRecorder } from '@eleven-labs/nest-profiler-http';

@Injectable()
export class WeatherService {
  constructor(private readonly recorder: HttpProfilerRecorder) {}

  async getForecast() {
    const url = 'https://api.weather.example.com/forecast';
    const startedAt = Date.now();
    const res = await fetch(url, { headers: { accept: 'application/json' } });
    const body = await res.json();

    this.recorder.capture({
      method: 'GET',
      url,
      startedAt,
      duration: Date.now() - startedAt,
      statusCode: res.status,
      responseHeaders: res.headers, // fetch `Headers` (and `Map`) are supported
      responseBody: body,
    });

    return body;
  }
}

For a reusable integration, implement HttpInstrumentation — a NestJS provider with install(recorder) — and add it to instrumentations. It can inject ModuleRef, config, etc. This is exactly how the bundled adapters work. Example, instrumenting got:

import { Injectable } from '@nestjs/common';
import type { HttpInstrumentation, HttpProfilerRecorder } from '@eleven-labs/nest-profiler-http';
import got from 'got';

@Injectable()
export class GotInstrumentation implements HttpInstrumentation {
  install(recorder: HttpProfilerRecorder): void {
    got.extend({
      hooks: {
        beforeRequest: [
          (options) => {
            (options as { _start?: number })._start = Date.now();
          },
        ],
        afterResponse: [
          (response) => {
            const started = (response.request.options as { _start?: number })._start ?? Date.now();
            recorder.capture({
              method: response.request.options.method,
              url: response.requestUrl.toString(),
              startedAt: started,
              duration: Date.now() - started,
              statusCode: response.statusCode,
              responseHeaders: response.headers,
              responseBody: response.body,
            });
            return response;
          },
        ],
      },
    });
  }
}

// HttpCollectorModule.forRoot({ instrumentations: [GotInstrumentation] });

Use record(entry) instead of capture(input) if you have already built a final HttpRequestEntry and want to bypass the capture options. The example API swaps its whole ArticleGateway between the axios and fetch adapters with HTTP_CLIENT=axios|fetch — run it with HTTP_CLIENT=fetch to see the fetch adapter capturing the same calls.

Options

HttpCollectorModule.forRoot(options) accepts:

OptionDefaultDescription
instrumentations[]The adapters to install (AxiosInstrumentation, FetchInstrumentation, …). Nothing is instrumented unless listed.
slowThreshold300Calls at/above this duration (ms) are tagged slow.
nPlusOneThreshold2Identical calls repeated ≥ N in one request are tagged n-plus-one.
chattyThreshold10A request making ≥ N outgoing calls is tagged chatty.
largePayloadThreshold1048576A call whose payload reaches this size (bytes) is tagged large-payload. 0 disables.
slowSeveritywarningSeverity of the slow tag ('info' | 'warning' | 'danger').
nPlusOneSeveritydangerSeverity of the n-plus-one tag.
chattySeveritywarningSeverity of the chatty tag.
largePayloadSeveritywarningSeverity of the large-payload tag.
error5xxWhat counts as a failed call: it threw, or answered ≥ 500. A 404 from an API you call is an answer, not a failure — pass { httpStatus: 400 } to count it. See What counts as an error.
captureRequestHeaderstrueCapture (and mask) outgoing request headers.
captureRequestBodyfalseCapture request body for non-GET/HEAD requests.
captureResponseHeaderstrueCapture (and mask) response headers.
captureResponseBodyfalseCapture response body — can be large.
maskHeaders[]Extra header names to redact (merged with the defaults).

What it collects

For each outgoing request: method, url, statusCode, duration, startedAt, optional error, and (per options) request/response headers and bodies.

Toolbar badge

Request count (e.g. 3). When errors are present: 3 (1 err).

Panel behaviour

The HTTP Client panel lets you expand each row to inspect request/response headers and bodies. That behaviour ships as a compiled, same-origin browser bundle (http.js) that the module registers with the profiler automatically — there is nothing to configure, and the templates carry no inline JavaScript. It is a reference implementation of the Extending the UI with JavaScript pattern, reusing the core window.NestProfiler runtime.

Powered & maintained by

On this page