@dudousxd/nestjs-telescope
Version:
Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.
181 lines (140 loc) • 6 kB
Markdown
---
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
`/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:
```bash
pnpm add /nestjs-telescope @dudousxd/nestjs-telescope-ui
```
Import both modules in your root module:
```ts
import { Module } from '@nestjs/common';
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
import { TelescopeUiModule } from '@dudousxd/nestjs-telescope-ui';
({
imports: [
TelescopeModule.forRoot({
// dev: rich capture, open gate. prod: flip enabled + supply an authorizer.
enabled: process.env.NODE_ENV !== 'production',
authorizer: () => true, // gates the API; defaults to DENY in production
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.
```ts
import { ConfigModule, ConfigService } from '@nestjs/config';
TelescopeModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
path: 'observability', // static — NOT returned by the factory
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`.
```ts
TelescopeModule.forRoot({
enabled: true,
authorizer: (ctx) => isAdmin(ctx.request), // your own check on the platform 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.
```ts
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
```ts
// Wrong — in production this 403s: no authorizer, and capture is off.
TelescopeModule.forRoot({});
```
```ts
// Correct — open the gate explicitly in prod.
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
```ts
// Wrong — the route is bound at build time; this `path` is ignored.
TelescopeModule.forRootAsync({
useFactory: () => ({ path: 'observability', enabled: true }),
});
```
```ts
// Correct — pass path as a static sibling of useFactory.
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 `/`
```ts
// Wrong — under app.setGlobalPrefix('api') the module middleware only sees '/'.
TelescopeModule.forRoot({ enabled: true });
```
```ts
// Correct — opt out of the module middleware and register capture globally.
import { TelescopeService, telescopeRequestCapture } from '@dudousxd/nestjs-telescope';
TelescopeModule.forRoot({ enabled: true, registerRequestMiddleware: false });
// in bootstrap, after creating the app:
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`.