Storage adapters
Persist Telescope entries in your own database (MikroORM — MySQL/SQLite) or share one store across replicas (Redis). Same StorageProvider contract everywhere.
The default store is per-process SQLite. Swap it by passing a storage provider to forRoot. See Storage for the SPI; this page is the wiring for each adapter.
MikroORM (MySQL / SQLite) — @dudousxd/nestjs-telescope-mikro-orm
Persists entries to your application's own database through MikroORM — so you can use your existing MySQL (or SQLite) for Telescope instead of standing up Redis. No migration, no manual table setup. Hand it your existing MikroORM:
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
import { MikroOrmStorageProvider } from '@dudousxd/nestjs-telescope-mikro-orm';
import { MikroORM } from '@mikro-orm/core';
@Module({
imports: [
// your normal MikroOrmModule.forRoot({ ... }) — nothing to add there
TelescopeModule.forRootAsync({
inject: [MikroORM],
useFactory: (orm: MikroORM) => ({
// borrows the host's connection config; opens its OWN scoped connection
storage: new MikroOrmStorageProvider(orm),
}),
}),
],
})
export class AppModule {}For high write volume, put Telescope on a separate database by passing explicit connection options instead of the host MikroORM:
import { MySqlDriver } from '@mikro-orm/mysql';
new MikroOrmStorageProvider({
driver: MySqlDriver,
clientUrl: process.env.TELESCOPE_DATABASE_URL,
// ensureSchema: false, // opt out of boot-time schema sync (you manage it via migration)
});How it works:
- Dedicated, scoped connection. The provider opens its own MikroORM that knows only
TelescopeEntry. The schema diff can therefore only ever touchtelescope_entries(it can't alter your other tables), and it avoids a self-capture loop where everyINSERT INTO telescope_entrieswould be recorded as a query. - Self-healing schema at boot.
init()runsensureDatabase()+schema.update({ safe: true })— additive only: creates the table if missing, adds missing columns, never drops anything.ensureSchema: falsedisables it. - Lifecycle.
init()is awaited byTelescopeModuleat startup;close()closes the owned connection at shutdown. You call neither. - Keyset pagination is newest-first; the cursor encodes a
(createdAt, id)position, so paging resumes even after older entries are pruned.
This same package ships the MikroORM query watcher and N+1 detector — see Watchers.
Redis (shared, multi-instance) — @dudousxd/nestjs-telescope-redis
The store to pick when you run multiple replicas: all replicas pointed at the same Redis share one store, so the dashboard aggregates entries across the whole deployment instead of one process. Requires Redis 6+ and ioredis v5+.
pnpm add @dudousxd/nestjs-telescope-redis ioredisimport Redis from 'ioredis';
import { RedisStorageProvider } from '@dudousxd/nestjs-telescope-redis';
import { TelescopeModule } from '@dudousxd/nestjs-telescope';
const storage = new RedisStorageProvider(new Redis(process.env.REDIS_URL));
TelescopeModule.forRoot({ storage });Notes:
-
The host owns the connection. The provider never closes the
ioredisclient it was given (close()is a no-op). Manage the connection's lifecycle andquit()yourself. -
Pruning is explicit. There's no per-key TTL — old entries are removed by the core pruner via the
pruneconfig (e.g.prune: { after: '24h' }). -
keyPrefix. Every key is namespaced; the default prefix istelescope:. Override it to share one Redis across apps:new RedisStorageProvider(redis, { keyPrefix: 'myapp:telescope:' });
Watchers
Query watchers (MikroORM, TypeORM, Prisma) and behavioral watchers (mail, cache, schedule, events, logs, redis, model) — each a small package you add to the watchers array.
Queue managers
Horizon-style live queue consoles — BullMQ (browse + retry/remove/promote/retry-all) and SQS (depth + DLQ inspection + redrive), all behind a default-deny mutation gate.