| name | telescope-setup |
| description | Mount nestjs-telescope in a NestJS app with TelescopeModule.forRoot / forRootAsync. Covers the enabled flag, the default-deny production API gate and authorizer, the shared path option (must match TelescopeUiModule), prune retention, swapping the default in-memory SQLite StorageProvider, and the setGlobalPrefix / registerRequestMiddleware + telescopeRequestCapture caveat. Use for "set up Telescope", "dashboard returns 403/404 in production", "forRootAsync config". |
| license | MIT |
| metadata | {"type":"core","library":"@dudousxd/nestjs-telescope","library_version":"1.12.0","framework":"nestjs"} |
Setting up nestjs-telescope
@dudousxd/nestjs-telescope is an observability console for NestJS. Importing the
module wires request and exception capture automatically and turns on a
zero-config in-memory SQLite store. Everything else (other watchers, storage,
auth, alerts) is layered on through forRoot options.
Setup
Install core plus the dashboard:
pnpm add @dudousxd/nestjs-telescope @dudousxd/nestjs-telescope-ui
Import both modules in your root module:
import { Module } from '@nestjs/common';
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
import { TelescopeUiModule } from '@dudousxd/nestjs-telescope-ui';
@Module({
imports: [
TelescopeModule.forRoot({
enabled: process.env.NODE_ENV !== 'production',
authorizer: () => true,
prune: { after: '24h' },
}),
TelescopeUiModule.forRoot(),
],
})
export class AppModule {}
Boot and open /telescope for the dashboard, or GET /telescope/api/entries for
the headless JSON API. The two modules MUST agree on path (default telescope).
Source: packages/core/src/nest/telescope.module.ts,
website/content/docs/getting-started.mdx.
Core patterns
Pattern 1 — async config with forRootAsync
Use a factory when options depend on ConfigService or other providers. The
path is a static sibling of useFactory — it is bound at module-build time
and is NOT read from the factory result.
import { ConfigModule, ConfigService } from '@nestjs/config';
TelescopeModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
path: 'observability',
useFactory: (config: ConfigService) => ({
enabled: config.get('TELESCOPE_ENABLED') === 'true',
authorizer: () => true,
prune: { after: '7d' },
}),
});
Source: packages/core/src/nest/telescope.module.ts (forRootAsync).
Pattern 2 — the production gate
In production the API gate is default-deny. To expose the dashboard in prod you
must BOTH set enabled: true AND supply an authorizer (or dashboardAuth). With
enabled false, capture stops; with no authorizer the API returns 403.
TelescopeModule.forRoot({
enabled: true,
authorizer: (ctx) => isAdmin(ctx.request),
});
The default authorizer allows when NODE_ENV !== 'production' and denies otherwise.
For a real login wall use the telescope-access-mcp skill (dashboardAuth).
Source: packages/core/src/nest/telescope.options.ts (authorizer),
website/content/docs/getting-started.mdx (production-gate callout).
Pattern 3 — retention with prune
prune.after is a duration string or number of ms. Add intervalMs to tune the
sweep cadence and keepLast to always retain the newest N regardless of age.
TelescopeModule.forRoot({
prune: { after: '24h', intervalMs: 60_000, keepLast: 1000 },
});
Per-type retention and archive-before-prune live in the
telescope-storage-retention skill.
Source: packages/core/src/config/options.ts (PruneOptions).
Common mistakes
Mistake 1 — expecting the dashboard to work in production with defaults
TelescopeModule.forRoot({});
TelescopeModule.forRoot({
enabled: process.env.NODE_ENV !== 'production' ? true : true,
authorizer: (ctx) => isAdmin(ctx.request),
});
Mechanism: the API gate denies when NODE_ENV === 'production' until you supply an
authorizer (or dashboardAuth), and capture is gated by enabled.
Source: packages/core/src/nest/telescope.options.ts.
Mistake 2 — passing path through the async factory
TelescopeModule.forRootAsync({
useFactory: () => ({ path: 'observability', enabled: true }),
});
TelescopeModule.forRootAsync({
path: 'observability',
useFactory: () => ({ enabled: true, authorizer: () => true }),
});
Mechanism: the controller route is bound at module-build time, so the factory's
returned path cannot move it. Source: packages/core/src/nest/telescope.module.ts.
Mistake 3 — using setGlobalPrefix and capturing only /
TelescopeModule.forRoot({ enabled: true });
import { TelescopeService, telescopeRequestCapture } from '@dudousxd/nestjs-telescope';
TelescopeModule.forRoot({ enabled: true, registerRequestMiddleware: false });
app.use(telescopeRequestCapture(app.get(TelescopeService)));
Mechanism: NestJS scopes module middleware (even a catch-all) to the prefixed route
table, so only / is captured; a raw app.use runs before all handlers regardless
of prefix. Source: packages/core/src/nest/telescope.options.ts
(registerRequestMiddleware), packages/core/src/nest/telescope.module.ts.