Aviary

Getting Started

Mount the Telescope dashboard in an existing NestJS app — install core + ui, import two modules, and open /telescope. Zero-config SQLite by default; swap the storage adapter when you're ready.

The fastest way to get Telescope running is two imports: the core module and the UI module. Request and exception capture wire themselves automatically, and a zero-config SQLite store is on by default — so you import, boot, and open /telescope. Most apps are done in under five minutes.


Prerequisites

  • Node.js 20+
  • NestJS 11+ for the UI module (core works on 10+)
  • Works on Express and Fastify

Step 1 — Install

Install core and the dashboard:

pnpm add @dudousxd/nestjs-telescope @dudousxd/nestjs-telescope-ui
npm install @dudousxd/nestjs-telescope @dudousxd/nestjs-telescope-ui

Step 2 — Import the modules

Add both modules to your root module. TelescopeModule.forRoot() wires request and exception capture and turns on the default SQLite store and pruner; TelescopeUiModule.forRoot() serves the dashboard SPA.

import { Module } from '@nestjs/common';
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
import { TelescopeUiModule } from '@dudousxd/nestjs-telescope-ui';

@Module({
  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 {}

Done. Boot the app and open http://localhost:3000/telescope. The dashboard polls /telescope/api/* and shows captured entries — type tabs plus live polling — and, on an entry, the correlated batch: the request and everything it caused.

Production gate

The API gate denies in production by default. In prod you must set enabled: true and supply an authorizer (or wire dashboard auth) — otherwise the dashboard returns 403. This is a default, not an afterthought: capture runs with redaction always on, and the gate is closed until you open it.


Step 3 — Hit the headless API

The dashboard is optional — everything it shows comes from a plain JSON API you can curl, script, or build your own admin against:

Method & pathReturns
GET /telescope/api/entriesA page of captured entries (filter by type, tag, batch, family, time).
GET /telescope/api/entries/:idOne entry plus its correlated batch — the request and everything it caused.
GET /telescope/api/pulse?window=1hPer-type counts, slowest entries, top exceptions, N+1 occurrences.
GET /telescope/api/queues?window=1hPer-queue throughput, runtime/wait-time percentiles, failure rate.
GET /telescope/api/timeseries?window=1h&buckets=60Bucketed throughput — the data behind the sparkline.
GET /telescope/api/healthTelescope's own self-overhead: capture cost, buffer pressure, flush stats, drops.

The zero-config SQLite default

Out of the box Telescope persists entries to a per-process SQLite store. No connection string, no migration, no table setup — it just works on the first boot. That's the right default for local development and single-process apps.

It is per-process, though: each replica has its own SQLite, so in a multi-instance deployment the dashboard would only show entries from the pod that served the request. When you scale out, pick a shared storage adapter.


Step 4 — Pick your storage adapter

Swap the default by passing a storage provider to forRoot. Every adapter implements the same StorageProvider SPI, so the API, dashboard, and pruner treat them identically.

AdapterUse it whenPackage
SQLite (default)Local dev, single process.built into core
MikroORM (MySQL / SQLite)You already run MikroORM and want Telescope in your own DB — no Redis.-mikro-orm
RedisMulti-instance: every replica reads/writes one shared store, so the dashboard aggregates the whole cluster.-redis
In-memoryTests.-testing

For example, to share one store across replicas via Redis:

import Redis from 'ioredis';
import { RedisStorageProvider } from '@dudousxd/nestjs-telescope-redis';

TelescopeModule.forRoot({
  storage: new RedisStorageProvider(new Redis(process.env.REDIS_URL)),
});

See Storage for the SPI and the storage packages for each adapter's wiring.


Step 5 — Add watchers

Core captures requests and exceptions on its own. Everything else — queries, jobs, mail, cache, schedules, events, logs, Redis commands — is a watcher you add to the watchers array. Each watcher is its own package; install the ones that match your stack.

To capture outbound HTTP (no peer dependency — it instruments the global fetch and sanitizes secret query params), add the built-in watcher:

import { TelescopeModule, HttpClientWatcher } from '@dudousxd/nestjs-telescope';

TelescopeModule.forRoot({ watchers: [new HttpClientWatcher({ slowMs: 1000 })] });

Calls through @nestjs/axios bypass fetch — pass an axios source to capture them too (see the HttpClientWatcher reference).

To capture queries from MikroORM, wire its logger so each query correlates to the request that triggered it:

import { TelescopeModule, TelescopeService } from '@dudousxd/nestjs-telescope';
import { telescopeMikroOrmLogger } from '@dudousxd/nestjs-telescope-mikro-orm';

MikroOrmModule.forRootAsync({
  inject: [TelescopeService],
  useFactory: (telescope: TelescopeService) => ({
    // ...your config
    debug: ['query'],
    loggerFactory: telescopeMikroOrmLogger((input) => telescope.record(input)),
  }),
});

Browse the Packages section for every watcher, storage, and queue manager — each page is a complete, copy-pasteable setup.


Next steps

On this page