Aviary
Recipes

Request context for your routes

Capture the authenticated user on each request with resolveUser, keep capture working under setGlobalPrefix via telescopeRequestCapture, and drop ad-hoc debug dumps with telescopeDump.

Two host-specific wrinkles and one debugging tool: tell Telescope how to read your authenticated user, register request capture correctly when your app uses a global prefix, and drop ad-hoc values into the dashboard from anywhere with telescopeDump.


Record the authenticated user — resolveUser

By default each captured request entry reads request.user (the Passport/guard convention) for its user field. If your app attaches the user somewhere else, point resolveUser at it:

TelescopeModule.forRoot({
  // Read the user from wherever your guard puts it. Return null/undefined for
  // anonymous. The resolved value is redacted by the Recorder like any content.
  resolveUser: (request) => {
    const session = (request as { session?: { user?: unknown } }).session;
    return session?.user ?? null;
  },
});

How it works

  • resolveUser(request) receives the raw platform request (Express or Fastify). Return the user object, or null/undefined for an anonymous request.
  • It never crashes capture — a hook that throws is treated as "no user" (null), not an error.
  • The result is redacted by the recorder like any other content, so a password/token field on the user object is masked automatically. Don't pre-strip it.

resolveUser only sets the user field on the request entry. It does not gate the dashboard — that's dashboardAuth. The two are unrelated.

Find everything a user did

Once a request carries a user, the built-in userTagger tags that request entry user:<id> automatically — no configuration. It reads content.user, preferring id, then _id (Mongo), then email, so whatever your resolveUser returns is enough to identify the actor.

Because tags are indexed and filterable end-to-end, this turns into a one-click pivot in the dashboard: every user-tagged entry shows a user:<id> chip in the entries table, and the entry detail view offers "View all activity for this user". Clicking either lands you on the entries list pre-filtered to that tag (#/entries?tag=user:<id>) — every request that user made, across types, in one view.

The entries pages also carry a dedicated User combobox over the user: tag namespace these tags create: it lists the ids userTagger emitted (with per-user entry counts), and selecting one renders a User: <id> chip instead of a raw tag. The "View all activity for this user" pivot feeds that same control, so a user:<id> arriving via ?tag= shows up as a User chip. See the dashboard tour.

// The userTagger is in BUILTIN_TAGGERS — nothing to wire up. A request whose
// resolved user is { id: 42 } is tagged "user:42"; the dashboard does the rest.

Capture under a global prefix — telescopeRequestCapture

If your app calls app.setGlobalPrefix('api'), NestJS scopes module middleware to the prefixed route table — so Telescope's auto-registered capture middleware only ever sees /, and request entries stop appearing. The fix is two steps: turn off the auto-registration and register capture globally in bootstrap.

// app.module.ts — opt out of the module-level middleware
import { TelescopeModule } from '@dudousxd/nestjs-telescope';

TelescopeModule.forRoot({
  registerRequestMiddleware: false, // we register it globally in main.ts instead
});
// main.ts — register the capture before listen()
import { NestFactory } from '@nestjs/core';
import {
  TelescopeService,
  telescopeRequestCapture,
} from '@dudousxd/nestjs-telescope';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.setGlobalPrefix('api');

  // Global capture sees EVERY route, prefix or not.
  app.use(telescopeRequestCapture(app.get(TelescopeService)));

  await app.listen(3000);
}
bootstrap();

How it works

  • telescopeRequestCapture(service) returns a framework-agnostic (req, res, next) handler — the same capture logic the module would have registered, but mounted globally via app.use(...) so the global prefix can't scope it away.
  • It still skips Telescope's own routes (the dashboard, its API, its assets) to avoid self-capturing the dashboard's polling — so you can mount it unconditionally.
  • Order doesn't matter much, but registering it before other global middleware means the request batch is open for the whole downstream execution, so everything a request causes correlates to it.

Only one capture path

Set registerRequestMiddleware: false whenever you call telescopeRequestCapture yourself — leaving the default true would register capture twice and open two batches per request. It's one or the other.


Ad-hoc debugging — telescopeDump

telescopeDump(value, label?) is console.log for the dashboard: it drops a value into the Dumps tab, correlated to the active request batch — so you can inspect exactly what a handler saw, in context, without a debugger.

import { telescopeDump } from '@dudousxd/nestjs-telescope';

@Injectable()
export class PricingService {
  quote(input: QuoteInput): Quote {
    const candidates = this.buildCandidates(input);

    telescopeDump(candidates, 'pricing candidates'); // shows up in Dumps, in this request's batch

    return this.pickBest(candidates);
  }
}

How it works

  • No dependency injection at the call site. telescopeDump is a free function — import and call it anywhere, including in shared code.
  • It's a no-op until Telescope is wired (and after shutdown), so importing it in a library that's sometimes used outside a Telescope-enabled app never crashes.
  • The dump correlates to the active batch. Called inside a request handler or job, the dump appears alongside that request's queries, cache ops, and exceptions — the same correlation view as everything else.

Dumps are a dev tool — they capture whatever you hand them (redacted like any content). Leave a few in hot paths during an investigation, then remove them; they're not meant to ship as permanent instrumentation (that's what watchers are for).

On this page