Skip to content

@forinda/kickjs-devtools ​

Development dashboard and introspection endpoints for a KickJS app: routes, the DI container, request metrics, runtime and memory monitoring, jobs, cron, and the plugin/adapter topology. Runs on every HTTP runtime (Express, Fastify, h3).

For a walkthrough of the dashboard and its tabs, see the DevTools guide. To add your own tab or publish events to the panel, use @forinda/kickjs-devtools-kit.

Install ​

bash
pnpm add -D @forinda/kickjs-devtools ws

Peer dependencies: @forinda/kickjs (>=8.6.0) and ws (^8.0.0, used by the event-bus WebSocket).

Entry points ​

Import pathExports
@forinda/kickjs-devtoolsDevToolsAdapter, DevToolsOptions (type), RequestLogEntry (type)

The package imports reflect-metadata itself.

DevToolsAdapter ​

The adapter that mounts the dashboard and its JSON endpoints, feeds request counters from the framework's onResponse / onError hooks, and registers the server-side event bus under DEVTOOLS_BUS.

typescript
const DevToolsAdapter: AdapterFactory<DevToolsOptions, DevToolsAdapterExtensions>

Built with defineAdapter(). Call it as DevToolsAdapter({ … }) and pass the result to bootstrap({ adapters: [...] }).

ts
import { bootstrap } from '@forinda/kickjs'
import { DevToolsAdapter } from '@forinda/kickjs-devtools'

bootstrap({
  modules,
  adapters: [
    DevToolsAdapter({
      secret: process.env.DEVTOOLS_SECRET,
      exposeConfig: true,
      configPrefixes: ['APP_', 'DATABASE_'],
    }),
  ],
})

DevToolsOptions ​

FieldTypeDefaultDescription
basePathstring'/_debug'Path the dashboard and every endpoint mount under.
managementbooleanfalseServe the dashboard on the management port (bootstrap({ management: { port } })) instead of the app's. Without one it stays on the app's port.
enabledbooleantrue only when NODE_ENV is development or testWhen false, nothing mounts and a no-op bus is registered under DEVTOOLS_BUS.
exposeConfigbooleanfalseMount GET {basePath}/config with environment variables.
configPrefixesstring[]['APP_', 'NODE_ENV']Env var prefixes whose values /config shows. The app's other declared variables read [REDACTED]; undeclared ones are left out.
onErrorRateExceeded(rate: number) => voidlogs a warningCalled when the server-error rate rises above errorRateThreshold.
errorRateThresholdnumber0.5Error rate (5xx / total requests) that triggers onErrorRateExceeded.
adaptersany[][]Peer adapters to read stats from (e.g. WsAdapter). Only a fallback: live adapters are read from the application when available.
secretstring | falserandom 16-byte hexAccess token. Every endpoint requires it as the x-devtools-token header or ?token= query param. Generated and logged at startup when omitted; false disables the guard.
runtime{ enabled?: boolean; intervalMs?: number; bufferSize?: number }{ enabled: true, intervalMs: 1000, bufferSize: 60 }Runtime sampler settings. With enabled: false the /runtime and /memory/stream endpoints answer 404.
requestLognumber200How many recent requests the Requests tab keeps. 0 keeps none. Requests to basePath itself are never logged.

DevToolsAdapterExtensions ​

The adapter instance carries its reactive state, for tests and peer adapters:

typescript
interface DevToolsAdapterExtensions {
  readonly requestCount: Ref<number> // all responses
  readonly errorCount: Ref<number> // status >= 500
  readonly clientErrorCount: Ref<number> // status 400–499
  readonly startedAt: Ref<number> // last mount time (ms)
  readonly errorRate: ComputedRef<number> // errorCount / requestCount
  readonly uptimeSeconds: ComputedRef<number> // process uptime
  readonly routeLatency: Record<string, RouteStats> // keyed 'GET /api/v1/users/:id'
  readonly runtimeSampler: RuntimeSampler | null // null when runtime.enabled is false
  readonly memoryAnalyzer: MemoryAnalyzer | null
}

RuntimeSampler and MemoryAnalyzer come from @forinda/kickjs-devtools-kit. Unmatched requests share one <unmatched> latency bucket per method.

ts
const devtools = DevToolsAdapter({ secret: false })
// …after some requests
devtools.requestCount.value
devtools.memoryAnalyzer?.health(devtools.runtimeSampler!.history())

The adapter also implements introspect(), so it appears in its own Topology tab.

Endpoints ​

All paths are relative to basePath (default /_debug) and all are token-guarded unless secret: false.

MethodPathReturns
GET/The dashboard page. The page loads without a token and sends it on its API calls; a ?token= given here must match.
GET/routes{ routes } — every mounted route with its middleware, validation schemas and context-contributor chain.
GET/sourceHandler file and line for ?controller=&handler=; 404 when unknown.
GET/container{ registrations, count } — DI registrations (HMR shadow entries excluded).
GET/requestsRecent requests as RequestLogEntry[], oldest first; ?since=<seq> returns only newer entries.
GET/metricsRequest / error counts, error rate, uptime, and per-route latency with percentiles and a histogram.
GET/healthhealthy / degraded plus each peer adapter's status, refreshed from its onHealthCheck() (with a timeout).
GET/stateSnapshot of the reactive state.
GET/runtimeLatest RuntimeSnapshot, its history, MemoryHealth, and process identity (Node version, pid, platform, HTTP runtime).
GET/runtime/streamSSE stream of runtime snapshots.
GET/memory/streamSSE stream of MemoryHealth.
POST/memory/snapshotWrites a V8 heap snapshot and streams the .heapsnapshot file. Blocks the event loop while writing; a concurrent call gets 503.
POST/memory/gcRuns global.gc() and returns { before, after, reclaimedBytes, elapsedMs }; 412 unless Node runs with --expose-gc.
GET/conditionsWhich adapters a when condition turned off, and why.
GET/startupStartup timeline — each boot phase and adapter hook.
GET/tabsCustom tabs collected from every plugin's and adapter's devtoolsTabs().
POST/tabs/runRuns a launch tab action: ?tab=&action=.
GET/jobsJob sources — every jobInspector() exposed by an adapter or plugin.
GET/jobs/listJobs: ?source=&queue=&state=&start=&end= (state defaults to failed, at most 100 per page).
GET/jobs/jobOne job: ?source=&queue=&id=.
POST/jobs/action?source=&queue=&action=&id=&state= — retry, remove, retryAll, clean, pause, resume. 501 when the inspector lacks it.
GET/cron{ jobs } — @Cron jobs.
POST/cron/runRuns a cron job now: ?name=.
GET/topologyTopologySnapshot — plugins, adapters, contributors and DI tokens, with each introspect() result.
GET/wsWebSocket adapter stats, or { enabled: false } without one.
GET/queuesQueue adapter stats, or { enabled: false } without one.
GET/graph{ nodes, edges } — the DI dependency graph.
GET/streamSSE stream of metrics and container changes.
GET/config/originsWhere each declared config variable's value comes from — names only, so it is served without exposeConfig.
GET/config{ config, origins } — prefixed values, other declared vars redacted. Only mounted with exposeConfig: true.
WS/_busEvent-bus WebSocket the dashboard subscribes to. Attached in afterStart; same token guard.

RequestLogEntry ​

One row of GET /requests.

typescript
interface RequestLogEntry {
  seq: number // increasing; a client holding a higher seq knows the app restarted
  at: number // response finish time, ms since epoch
  method: string
  path: string
  route?: string // matched route pattern; absent when unmatched
  status: number
  durationMs: number
  requestId?: string
  error?: { name: string; message: string }
}

See also ​

Released under the MIT License. Built with TypeScript — runs on Express, Fastify, or h3.