npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

adonisjs-server-stats

v1.2.1

Published

Real-time server monitoring for AdonisJS v6 applications

Readme

adonisjs-server-stats

npm version npm downloads bundle size license TypeScript AdonisJS

A Laravel Telescope-inspired dev toolbar and real-time server monitor for AdonisJS v6.

Drop a single Edge tag into your layout and get a live stats bar showing CPU, memory, requests/sec, database pool, Redis, queues, and logs -- plus a full debug toolbar with SQL query inspection, event tracing, route listing, live log tailing, and custom panels.

Zero frontend dependencies. Zero build step. Just @serverStats() and go.

adonisjs-server-stats demo

Screenshots

Debug toolbar -- expandable panels for deep inspection:

| Queries | Events | |---------|--------| | Queries panel showing SQL queries with duration and model info | Events panel showing application events with payload data |

| Routes | Logs | |--------|------| | Routes panel showing all registered routes with handlers | Logs panel with level filtering and request ID correlation |

| Emails (custom pane) | |----------------------| | Emails panel showing sent emails with delivery status |

Features

  • Live stats bar -- CPU, memory, event loop lag, HTTP throughput, DB pool, Redis, queues, logs
  • Debug toolbar -- SQL queries, events, emails, routes, logs with search and filtering
  • Request tracing -- per-request waterfall timeline showing DB queries, events, and custom spans
  • Custom panes -- add your own tabs (webhooks, emails, cache, anything) with a simple config
  • Pluggable collectors -- use built-in collectors or write your own
  • Visibility control -- show only to admins, specific roles, or in dev mode
  • SSE broadcasting -- real-time updates via AdonisJS Transmit
  • Prometheus export -- expose all metrics as Prometheus gauges
  • Self-contained -- inline HTML/CSS/JS Edge tag, no React, no external assets
  • Graceful degradation -- missing optional dependencies are handled automatically

Installation

npm install adonisjs-server-stats

Quick Start

1. Register providers

// adonisrc.ts
providers: [
  {
    file: () => import('adonisjs-server-stats/provider'),
    environment: ['web'],
  },
  {
    file: () => import('adonisjs-server-stats/log-stream/provider'),
    environment: ['web'],
  },
]

2. Register middleware

// start/kernel.ts
server.use([
  () => import('adonisjs-server-stats/middleware'),
])

3. Create config

// config/server_stats.ts
import { defineConfig } from 'adonisjs-server-stats'
import { processCollector, systemCollector, httpCollector } from 'adonisjs-server-stats/collectors'

export default defineConfig({
  collectors: [
    processCollector(),
    systemCollector(),
    httpCollector(),
  ],
})

That's it -- this gives you CPU, memory, event loop lag, and HTTP throughput out of the box. All other options have sensible defaults. Add more collectors as needed:

// config/server_stats.ts
import env from '#start/env'
import { defineConfig } from 'adonisjs-server-stats'
import {
  processCollector,
  systemCollector,
  httpCollector,
  dbPoolCollector,
  redisCollector,
  queueCollector,
  logCollector,
  appCollector,
} from 'adonisjs-server-stats/collectors'

export default defineConfig({
  // How often to collect and broadcast stats (in milliseconds)
  intervalMs: 3000,

  // Real-time transport: 'transmit' for SSE via @adonisjs/transmit, 'none' for polling only
  transport: 'transmit',

  // Transmit channel name clients subscribe to
  channelName: 'admin/server-stats',

  // HTTP endpoint that serves the latest stats snapshot (set to false to disable)
  endpoint: '/admin/api/server-stats',

  collectors: [
    // CPU usage, event loop lag, heap/RSS memory, uptime, Node.js version
    processCollector(),

    // OS load averages, total/free system memory, system uptime
    systemCollector(),

    // Requests/sec, avg response time, error rate, active connections
    // maxRecords: size of the circular buffer for request tracking
    httpCollector({ maxRecords: 10_000 }),

    // Lucid connection pool: used/free/pending/max connections
    // Requires @adonisjs/lucid
    dbPoolCollector({ connectionName: 'postgres' }),

    // Redis server stats: memory, connected clients, keys, hit rate
    // Requires @adonisjs/redis
    redisCollector(),

    // BullMQ queue stats: active/waiting/delayed/failed jobs
    // Requires bullmq -- connects directly to Redis (not via @adonisjs/redis)
    queueCollector({
      queueName: 'default',
      connection: {
        host: env.get('QUEUE_REDIS_HOST'),
        port: env.get('QUEUE_REDIS_PORT'),
        password: env.get('QUEUE_REDIS_PASSWORD'),
      },
    }),

    // Log file stats: errors/warnings in a 5-minute window, entries/minute
    logCollector({ logPath: 'logs/adonisjs.log' }),

    // App-level metrics: online users, pending webhooks, pending emails
    // Requires @adonisjs/lucid
    appCollector(),
  ],
})

4. Add a route

// start/routes.ts
router
  .get('/admin/api/server-stats', '#controllers/admin/server_stats_controller.index')
  .use(middleware.superadmin()) // Replace with your own middleware

5. Create the controller

// app/controllers/admin/server_stats_controller.ts
import app from '@adonisjs/core/services/app'
import type { HttpContext } from '@adonisjs/core/http'
import type { StatsEngine } from 'adonisjs-server-stats'

export default class ServerStatsController {
  async index({ response }: HttpContext) {
    const engine = await app.container.make('server_stats.engine') as StatsEngine
    return response.json(engine.getLatestStats())
  }
}

6. Render the stats bar

Edge (add before </body>):

@serverStats()

Config Reference

ServerStatsConfig

| Option | Type | Default | Description | |---------------|------------------------|-----------------------------|--------------------------------------------| | intervalMs | number | 3000 | Collection + broadcast interval (ms) | | transport | 'transmit' \| 'none' | 'transmit' | SSE transport. 'none' = poll-only. | | channelName | string | 'admin/server-stats' | Transmit channel name | | endpoint | string \| false | '/admin/api/server-stats' | HTTP endpoint. false to disable. | | collectors | MetricCollector[] | [] | Array of collector instances | | skipInTest | boolean | true | Skip collection during tests | | onStats | (stats) => void | -- | Callback after each collection tick | | shouldShow | (ctx) => boolean | -- | Per-request visibility guard | | devToolbar | DevToolbarOptions | -- | Dev toolbar configuration |

DevToolbarOptions

| Option | Type | Default | Description | |------------------------|-----------------|---------|------------------------------------------------| | enabled | boolean | false | Enable the dev toolbar | | maxQueries | number | 500 | Max SQL queries to buffer | | maxEvents | number | 200 | Max events to buffer | | maxEmails | number | 100 | Max emails to buffer | | slowQueryThresholdMs | number | 100 | Slow query threshold (ms) | | persistDebugData | boolean \| string | false | Persist debug data to disk across restarts. true writes to .adonisjs/server-stats/debug-data.json, or pass a custom path. | | tracing | boolean | false | Enable per-request tracing with timeline visualization | | maxTraces | number | 200 | Max request traces to buffer | | panes | DebugPane[] | -- | Custom debug panel tabs |


Collectors

Each collector is a factory function that returns a MetricCollector. All collectors run in parallel each tick; missing peer dependencies are handled gracefully (the collector returns defaults instead of crashing).

Built-in Collectors

| Collector | Metrics | Options | Peer Deps | |--------------------------|-------------------------------------------------------------|------------|---------------------| | processCollector() | CPU %, event loop lag, heap/RSS memory, uptime, Node version | none | -- | | systemCollector() | OS load averages, system memory, system uptime | none | -- | | httpCollector(opts?) | Requests/sec, avg response time, error rate, active connections | optional | -- | | dbPoolCollector(opts?) | Pool used/free/pending/max connections | optional | @adonisjs/lucid | | redisCollector() | Status, memory, clients, keys, hit rate | none | @adonisjs/redis | | queueCollector(opts) | Active/waiting/delayed/failed jobs, worker count | required | bullmq | | logCollector(opts) | Errors/warnings/entries (5m window), entries/minute | required | -- | | appCollector() | Online users, pending webhooks, pending emails | none | @adonisjs/lucid |

Collector Options

httpCollector({
  maxRecords: 10_000,  // Circular buffer size (default: 10,000)
  windowMs: 60_000,    // Rolling window for rate calc (default: 60s)
})

dbPoolCollector({
  connectionName: 'postgres',  // Lucid connection name (default: 'postgres')
})

queueCollector({
  queueName: 'default',
  connection: {
    host: 'localhost',
    port: 6379,
    password: 'secret',
  },
})

logCollector({
  logPath: 'logs/adonisjs.log',
})

Custom Collectors

Implement the MetricCollector interface to create your own:

import type { MetricCollector } from 'adonisjs-server-stats'

function diskCollector(): MetricCollector {
  return {
    name: 'disk',
    async collect() {
      const { availableSpace, totalSpace } = await getDiskInfo()
      return {
        diskAvailableGb: availableSpace / 1e9,
        diskTotalGb: totalSpace / 1e9,
        diskUsagePercent: ((totalSpace - availableSpace) / totalSpace) * 100,
      }
    },
  }
}

// config/server_stats.ts
export default defineConfig({
  collectors: [
    processCollector(),
    diskCollector(),  // mix with built-in collectors
  ],
})

The MetricCollector interface:

interface MetricCollector {
  name: string
  start?(): void | Promise<void>
  stop?(): void | Promise<void>
  collect(): Record<string, MetricValue> | Promise<Record<string, MetricValue>>
}

Visibility Control (shouldShow)

By default the stats bar renders for every request. Use shouldShow to restrict it. The callback receives the AdonisJS HttpContext and should return true to show the bar, false to hide it.

Because shouldShow runs after middleware (including auth), you have full access to ctx.auth.

export default defineConfig({
  // Only show in development
  shouldShow: () => env.get('NODE_ENV'),
})
export default defineConfig({
  // Only show for logged-in admin users
  shouldShow: (ctx) => ctx.auth?.user?.isAdmin === true,
})
export default defineConfig({
  // Only show for specific roles
  shouldShow: (ctx) => {
    const role = ctx.auth?.user?.role
    return role === 'admin' || role === 'superadmin'
  },
})

Tip: When shouldShow is not set, the bar renders for everyone. In production you almost always want to set this.


Edge Tag

The @serverStats() Edge tag renders a self-contained stats bar with inline HTML, CSS, and JS -- no external assets, no build step.

<body>
  @inertia()
  @serverStats()
</body>

Features:

  • Polls the stats API at the configured interval
  • Color-coded thresholds (green/amber/red)
  • SVG sparkline charts with gradient fills
  • Hover tooltips with min/max/avg stats
  • Show/hide toggle (persisted via localStorage)
  • Auto-hides for non-admin users (403 detection)
  • Scoped CSS (.ss- prefix)
  • Stale connection indicator (amber dot after 10s)

Dev Toolbar

Adds a debug panel with SQL query inspection, event tracking, email capture with HTML preview, route table, live logs, and per-request tracing. Only active in non-production environments.

export default defineConfig({
  devToolbar: {
    enabled: true,
    maxQueries: 500,
    maxEvents: 200,
    maxEmails: 100,
    slowQueryThresholdMs: 100,
    persistDebugData: true,  // or a custom path: 'custom/debug.json'
    tracing: true,           // enable per-request timeline
  },
})

Register the debug API routes:

// start/routes.ts
router
  .group(() => {
    router.get('queries', '#controllers/admin/debug_controller.queries')
    router.get('events', '#controllers/admin/debug_controller.events')
    router.get('routes', '#controllers/admin/debug_controller.routes')
    router.get('emails', '#controllers/admin/debug_controller.emails')
    router.get('emails/:id/preview', '#controllers/admin/debug_controller.emailPreview')
    router.get('traces', '#controllers/admin/debug_controller.traces')
    router.get('traces/:id', '#controllers/admin/debug_controller.traceDetail')
  })
  .prefix('/admin/api/debug')
  .use(middleware.admin())

Built-in Emails Tab

The debug toolbar captures all emails sent via AdonisJS mail (mail:sending, mail:sent, mail:queued, queued:mail:error events). Click any email row to preview its HTML in an iframe.

Persistent Debug Data

Enable persistDebugData: true to save queries, events, and emails to .adonisjs/server-stats/debug-data.json. You can also pass a custom path (relative to app root) like persistDebugData: 'custom/debug.json'. Data is:

  • Loaded on server startup (before collectors start)
  • Flushed every 30 seconds (handles crashes)
  • Saved on graceful shutdown

Request Tracing

When tracing: true is set, the debug panel gains a Timeline tab that shows a waterfall view of every HTTP request -- which DB queries ran, in what order, and how long each took.

Tracing uses AsyncLocalStorage to automatically correlate operations to the request that triggered them. DB queries captured via db:query events and console.warn calls are automatically attached to the active request trace.

How it works

GET /organizations/create    286ms
├─ SELECT * FROM users          2ms  █
├─ SELECT * FROM orgs           4ms    █
├─ fetchMembers (custom)      180ms    ██████████████████
└─ response sent                5ms                      ██
  1. The Timeline tab shows a list of recent requests with method, URL, status code, duration, span count, and any warnings
  2. Click a request to see the waterfall chart -- each span is a horizontal bar positioned by time offset, color-coded by category
  3. Spans can be nested (a custom span wrapping DB queries will show them indented)

Span categories

| Category | Color | Auto-captured | |----------|--------|---------------| | DB | Purple | db:query events | | Request | Blue | Full request lifecycle | | Mail | Green | -- | | Event | Amber | -- | | View | Cyan | -- | | Custom | Gray | Via trace() helper |

Custom spans

Use the trace() helper to wrap any async code in a named span:

import { trace } from 'adonisjs-server-stats'

// In a controller or service:
const result = await trace('organization.fetchMembers', async () => {
  return OrganizationService.getMembers(orgId)
})

If tracing is disabled or no request is active, trace() executes the function directly with no overhead.

Custom Debug Panes

Add custom tabs to the debug panel:

import { defineConfig } from 'adonisjs-server-stats'
import type { DebugPane } from 'adonisjs-server-stats'

const webhooksPane: DebugPane = {
  id: 'webhooks',
  label: 'Webhooks',
  endpoint: '/admin/api/debug/webhooks',
  columns: [
    { key: 'id', label: '#', width: '40px' },
    { key: 'event', label: 'Event', searchable: true },
    { key: 'url', label: 'URL', searchable: true },
    {
      key: 'status',
      label: 'Status',
      width: '80px',
      format: 'badge',
      badgeColorMap: { delivered: 'green', pending: 'amber', failed: 'red' },
    },
    { key: 'duration', label: 'Duration', width: '70px', format: 'duration' },
    { key: 'timestamp', label: 'Time', width: '80px', format: 'timeAgo' },
  ],
  search: { placeholder: 'Filter webhooks by event or URL...' },
  clearable: true,
}

export default defineConfig({
  devToolbar: {
    enabled: true,
    panes: [webhooksPane],
  },
})

The endpoint must return JSON with the data array under a key matching the pane id (or dataKey):

// Controller
async webhooks({ response }: HttpContext) {
  const events = await WebhookEvent.query().orderBy('created_at', 'desc').limit(200)
  return response.json({ webhooks: events })
}

DebugPane Options

| Option | Type | Default | Description | |-------------|---------------------|---------|----------------------------------------------| | id | string | -- | Unique identifier (also default data key) | | label | string | -- | Tab display name | | endpoint | string | -- | API endpoint URL | | columns | DebugPaneColumn[] | -- | Column definitions | | search | { placeholder } | -- | Enable search bar | | dataKey | string | id | JSON key for data array (dot notation OK) | | fetchOnce | boolean | false | Cache after first fetch | | clearable | boolean | false | Show Clear button |

DebugPaneColumn Options

| Option | Type | Default | Description | |-----------------|--------------------------|----------|------------------------------------------| | key | string | -- | JSON field name | | label | string | -- | Column header text | | width | string | auto | CSS width (e.g. '60px') | | format | DebugPaneFormatType | 'text' | Cell format (see table below) | | searchable | boolean | false | Include in search filtering | | filterable | boolean | false | Click to set as search filter | | badgeColorMap | Record<string, string> | -- | Value-to-color map for badge format |

Format Types

| Format | Renders As | Expected Input | |------------|----------------------------------------|-------------------------| | text | Escaped plain text | any | | time | HH:MM:SS.mmm | Unix timestamp (ms) | | timeAgo | 3s ago, 2m ago | Unix timestamp (ms) | | duration | X.XXms with color coding | number (ms) | | method | HTTP method pill badge | 'GET', 'POST', etc. | | json | Compact preview, click to expand | object or array | | badge | Colored pill via badgeColorMap | string |

Badge colors: green, amber, red, blue, purple, muted


Prometheus Integration

Export all metrics as Prometheus gauges. Requires @julr/adonisjs-prometheus.

// config/prometheus.ts
import { defineConfig } from '@julr/adonisjs-prometheus'
import { httpCollector } from '@julr/adonisjs-prometheus/collectors/http_collector'
import { serverStatsCollector } from 'adonisjs-server-stats/prometheus'

export default defineConfig({
  endpoint: '/metrics',
  collectors: [httpCollector(), serverStatsCollector()],
})

Gauges are updated automatically on each collection tick.


Log Stream

The log stream module watches a JSON log file and broadcasts new entries via Transmit (SSE).

Two purposes:

  1. Provides error/warning counts to the stats bar via logCollector()
  2. Broadcasts individual log entries to a Transmit channel via LogStreamProvider

Standalone usage:

import { LogStreamService } from 'adonisjs-server-stats/log-stream'

const service = new LogStreamService('logs/app.log', (entry) => {
  console.log('New log entry:', entry)
})

await service.start()
// later...
service.stop()

TypeScript

All types are exported for consumer use:

// Core types
import type {
  ServerStats,
  ServerStatsConfig,
  MetricCollector,
  MetricValue,
  LogStats,
  DevToolbarOptions,
} from 'adonisjs-server-stats'

// Debug types
import type {
  DebugPane,
  DebugPaneColumn,
  DebugPaneFormatType,
  DebugPaneSearch,
  BadgeColor,
  QueryRecord,
  EventRecord,
  EmailRecord,
  RouteRecord,
  TraceSpan,
  TraceRecord,
} from 'adonisjs-server-stats'

// Trace helper
import { trace } from 'adonisjs-server-stats'

// Collector option types
import type {
  HttpCollectorOptions,
  DbPoolCollectorOptions,
  QueueCollectorOptions,
  QueueRedisConnection,
  LogCollectorOptions,
} from 'adonisjs-server-stats/collectors'

Peer Dependencies

All integrations use lazy import() -- missing peer deps won't crash the app. The corresponding collector simply returns defaults.

| Dependency | Required By | |-----------------------------|-----------------------------------| | @adonisjs/core | Everything (required) | | @adonisjs/lucid | dbPoolCollector, appCollector | | @adonisjs/redis | redisCollector | | @adonisjs/transmit | Provider (SSE broadcast) | | @julr/adonisjs-prometheus | serverStatsCollector | | bullmq | queueCollector | | edge.js | Edge tag |

License

MIT