vitals-node
v1.1.2
Published
Multi-framework health monitoring & system metrics for NestJS, Express, and Bun
Maintainers
Readme
vitals-node
Multi-framework health monitoring and AI agent integration for Node.js. Supports NestJS, Express, and Bun with zero-config setup.
Features
- Multi-framework — NestJS, Express, Bun
- Mode system —
monitor(health/metrics) ·agent(AI integration) ·both - Real-time dashboard — Charts, dark mode, route panel
- System metrics — CPU, Memory, Disk monitoring
- Route discovery — Auto-detect all registered API routes
- AI schema analysis — Claude or Ollama reads source code and infers request schemas
- Manual registry push — POST route list + schemas to a central server on demand
call_apibuilt-in tool — AI can invoke any route on this service directly- Alert notifications — LINE, Email, Telegram
- Prometheus export — Ready for Grafana/Prometheus
- Prisma integration — Database query monitoring + auto-generate agent tools
- Env auto-config — Zero code change via
VITALS_*environment variables - TypeScript first — Full type safety
Installation
npm install vitals-nodeQuick Start
NestJS
import { Module } from '@nestjs/common';
import { VitalsPulseModule } from 'vitals-node';
@Module({
imports: [
VitalsPulseModule.forRoot({
mode: 'both',
agent: { enabled: true, apiKey: process.env.VITALS_AGENT_API_KEY }
})
]
})
export class AppModule {}With mode: 'both' NestJS registers two controllers automatically:
HealthController— mounts at/healthAgentsController— mounts at/agents
Express
import express from 'express';
import { expressPulse } from 'vitals-node/express';
const app = express();
app.use(express.json());
app.use('/health', expressPulse({ app, mode: 'monitor' }));
app.use('/agents', expressPulse({ app, mode: 'agent', agent: { apiKey: process.env.VITALS_AGENT_API_KEY } }));
app.listen(3000);Bun
import { bunPulse } from 'vitals-node/bun';
const pulse = bunPulse({ mode: 'both' });
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname.startsWith('/health')) return pulse.handler(req);
return new Response('OK');
}
});Mode System
| Mode | What it enables | Metrics interval |
|---|---|---|
| monitor | health, metrics, dashboard, prometheus, alerts | runs |
| agent | route discovery, AI tools, registry push, call_api | skipped (saves resources) |
| both | all features (default) | runs |
In NestJS mode: 'both' creates two separate controllers: HealthController at /health and AgentsController at /agents. In Express/Bun you mount each middleware at the path you choose.
Endpoints
Monitor endpoints
Available when mode: 'monitor' or 'both' (mounted at your chosen base path, e.g. /health):
| Endpoint | Description |
|---|---|
| GET /health | Liveness probe |
| GET /health/detailed | Full system metrics + health checks |
| GET /health/dashboard | Real-time HTML dashboard |
| GET /health/metrics | Prometheus format |
| GET /health/prometheus | Prometheus format (alias) |
| GET /health/history | Metrics history |
| GET /health/alerts | Alert history |
| POST /health/test-alerts | Test notification channels |
Agent endpoints
Available when mode: 'agent' or 'both'. NestJS uses /agents/*; Express/Bun use the path you mount with mode: 'agent':
| Endpoint | Description |
|---|---|
| GET /agents/routes | All discovered routes with inferred schemas |
| GET /agents/tools | Agent tool schemas (Anthropic format) |
| POST /agents/invoke | Invoke an agent tool { name, input } |
| POST /agents/registry/push | Manually push registry to a webhook |
All agent endpoints require Authorization: Bearer <apiKey> when agent.apiKey is set.
In Express/Bun with mode: 'both', agent endpoints are nested under the monitor base path as /health/agent/tools, /health/agent/invoke, and /health/agent/registry/push.
NestJS Controllers
VitalsPulseModule.forRoot() registers controllers based on mode:
mode: 'monitor' → HealthController at /health
mode: 'agent' → AgentsController at /agents
mode: 'both' → both controllers (default)You can import the controllers directly for custom configuration:
import { HealthController, AgentsController, PULSE_MONITOR } from 'vitals-node';Environment Variables
Target projects only need VITALS_AGENT_API_KEY — all other config is passed in request bodies.
| Variable | Description |
|---|---|
| VITALS_MODE | monitor · agent · both |
| VITALS_PATH | Base path override |
| VITALS_AGENT_ENABLED | true to enable agent endpoints |
| VITALS_AGENT_API_KEY | Bearer token for /agents/* protection |
Manual Registry Push
Registry push is triggered on demand via POST /agents/registry/push. No config is stored in the target project — all parameters come from the request body.
POST /agents/registry/push
Authorization: Bearer <apiKey>
{
"webhook": "https://central-server.com/register", // optional — omit to get data in response only
"groupId": "my-company", // optional — group multiple services together
"projectId": "my-service",
"projectPath": "/api", // optional — override auto-detected base path
"tags": "crm,billing", // optional — help AI categorize this service
"apiKey": "central-server-bearer-token",
"baseUrl": "https://my-service.example.com",
"schemaAnalysis": { // optional — run AI schema analysis before push
"provider": "anthropic",
"apiKey": "sk-ant-xxx",
"model": "claude-haiku-4-5-20251001"
}
}Response always includes the full payload (same data sent to webhook):
{
"success": true,
"groupId": "my-company",
"projectId": "my-service",
"baseUrl": "https://my-service.example.com",
"projectPath": "/api",
"tags": "crm,billing",
"routes": [...],
"agentTools": [...],
"registeredAt": 1748247600000
}If webhook is omitted the push is skipped but the response still contains the full payload — useful for inspecting what would be sent.
AI Schema Analysis
Pass schemaAnalysis in the POST /agents/registry/push body to run schema inference before pushing. No API keys are stored in the target project.
Using Anthropic Claude
{
"schemaAnalysis": {
"provider": "anthropic",
"apiKey": "sk-ant-xxx",
"model": "claude-haiku-4-5-20251001",
"concurrency": 5,
"timeout": 30000
}
}Using Ollama (local LLM)
{
"schemaAnalysis": {
"provider": "ollama",
"baseUrl": "http://localhost:11434",
"model": "llama3.2",
"concurrency": 1,
"timeout": 120000
}
}How it works
- Routes are discovered from the running app
- vitals finds each handler's source file
- Source code is sent to the LLM in batches
- LLM returns body / params / query schema as JSON
- Schemas are included in
/agents/routesand the push payload
Example schema output
{
"method": "POST",
"path": "/credits/adjust",
"handler": "adjustCredit",
"schema": {
"description": "Adjust credit balance for a user account",
"body": {
"userId": { "type": "string", "description": "Target user ID", "required": true },
"amount": { "type": "number", "description": "Amount — positive adds, negative deducts", "required": true },
"reason": { "type": "string", "description": "Reason for adjustment", "required": false }
}
}
}call_api Built-in Tool
When vitals starts, it auto-registers a call_api tool that lets the AI invoke any route on the service directly.
POST /agents/invoke
Authorization: Bearer <apiKey>
{
"name": "call_api",
"input": {
"method": "POST",
"path": "/api/auth/login",
"headers": { "x-api-key": "service-key" },
"body": { "username": "admin", "password": "secret" }
}
}
→ { "result": { "status": 200, "ok": true, "body": { "token": "..." } } }Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| method | string | yes | GET POST PUT PATCH DELETE |
| path | string | yes | API path e.g. /api/users/123 |
| body | object | no | Request body for POST/PUT/PATCH |
| headers | object | no | Extra headers — use for auth cookies, API keys |
| query | object | no | Query string params |
Handling cookie-protected routes
For endpoints behind @UseGuards(CookieAuthGuard), pass the cookie in headers:
{
"name": "call_api",
"input": {
"method": "GET",
"path": "/user/info/full",
"headers": { "Cookie": "accessToken=<token_value>" }
}
}The AI can first call a login endpoint to obtain the token, then use it in subsequent calls.
AI Agent Flow
customer reports problem
→ chat → AI agent (Claude)
→ GET /agents/routes — discover what APIs exist and their schemas
→ GET /agents/tools — discover invokable diagnostic tools
→ POST /agents/invoke — call the right tool or API to fix the problem
→ response to customerExample: agent fixes a credit mismatch
// AI discovers routes and tools, then calls:
POST /agents/invoke
{ "name": "adjust_credit", "input": { "userId": "U123", "amount": 100 } }
→ { "result": { "success": true, "newBalance": 500 } }
// Or directly calls the API:
POST /agents/invoke
{ "name": "call_api", "input": { "method": "POST", "path": "/credits/adjust", "body": { "userId": "U123", "amount": 100 } } }
→ { "result": { "status": 200, "ok": true, "body": { "success": true } } }Route Discovery
NestJS
Routes are captured automatically via OnApplicationBootstrap lifecycle hook.
Express
Discovery is lazy — routes registered after the middleware call are still captured.
app.use('/health', expressPulse({ app }));
// All routes registered below are still discovered
app.get('/api/orders', handler);Fastify
monitor.discoverRoutes(fastifyApp, 'fastify');Manual Registration
import { createMonitor } from 'vitals-node/core';
const monitor = createMonitor();
monitor.registerRoute({ method: 'GET', path: '/api/orders', handler: 'OrderController.list' });
monitor.registerRoutes([
{ method: 'POST', path: '/api/orders' },
{ method: 'PUT', path: '/api/orders/:id/status' }
]);
// Manual schema annotation — overrides AI analysis for this route
monitor.annotateRoute('POST', '/api/orders', {
description: 'Create a new order',
body: {
userId: { type: 'string', description: 'User ID', required: true },
items: { type: 'array', description: 'Order items', required: true }
}
});Configuration Reference
interface VitalsConfig {
mode?: 'monitor' | 'agent' | 'both'; // default: 'both'
path?: string; // default: '/health'
dashboard?: boolean; // default: true
collectInterval?: number; // ms (default: 5000)
timezone?: string; // default: 'Asia/Bangkok'
prometheus?: boolean; // default: true
alerts?: {
thresholds?: {
cpu?: number; // default: 80%
memory?: number; // default: 90%
disk?: number; // default: 85%
responseTime?: number; // default: 3000ms
};
cooldown?: number; // ms between alerts (default: 5 min)
line?: { token: string; enabled?: boolean };
email?: { host: string; port: number; user: string; password: string; from: string; to: string[]; secure?: boolean };
telegram?: { botToken: string; chatId: string; enabled?: boolean };
};
healthChecks?: HealthCheckDefinition[];
agent?: {
enabled?: boolean; // must be true to expose /agent/* endpoints
apiKey?: string; // Bearer token protecting agent endpoints
tools?: AgentToolDefinition[];
};
}schemaAnalysis and registry config are passed in the POST /agents/registry/push request body, not in startup config — this keeps secrets out of the target project's environment.
Custom Health Checks
monitor.addHealthCheck({
name: 'redis',
critical: true,
timeout: 5000,
check: async () => {
const start = Date.now();
try {
await redis.ping();
return { name: 'redis', status: 'healthy', responseTime: Date.now() - start };
} catch (err) {
return { name: 'redis', status: 'unhealthy', responseTime: Date.now() - start, error: err.message };
}
}
});Agent Tools
Register custom tools that the AI can invoke via POST /agents/invoke:
import { getMonitor } from 'vitals-node/core';
getMonitor().registerAgentTool({
name: 'adjust_credit',
description: 'Adjust user credit balance',
parameters: {
userId: { type: 'string', description: 'User ID', required: true },
amount: { type: 'number', description: 'Amount to adjust (+/-)', required: true }
},
handler: async ({ userId, amount }) =>
creditService.adjust(String(userId), Number(amount))
});Tool schemas follow the Anthropic input_schema format — compatible with Claude tool use directly.
Prisma Integration
import { PrismaClient } from '@prisma/client';
import { prismaMiddleware, createPrismaHealthCheck, createPrismaAgentTools } from 'vitals-node/prisma';
import { getMonitor } from 'vitals-node/core';
const prisma = new PrismaClient();
prisma.$use(prismaMiddleware({ slowQueryThreshold: 1000, logSlowQueries: true }));
const monitor = getMonitor();
monitor.addHealthCheck(createPrismaHealthCheck(prisma));
const tools = createPrismaAgentTools(prisma, {
dmmf: Prisma.dmmf,
models: ['User', 'CreditTransaction'],
sensitiveFields: ['internalNote']
});
monitor.registerAgentTools(tools);System Metrics
{
"cpu": { "usage": 45.5, "cores": 8, "model": "Intel...", "speed": 3.2 },
"memory": { "total": 16384, "used": 8192, "free": 8192, "percentage": 50 },
"disk": { "total": 512000, "used": 256000, "free": 256000, "percentage": 50 },
"process": { "uptime": 86400, "pid": 1234, "nodeVersion": "v20.0.0", "memoryUsage": 128, "cpuUsage": 2.5 }
}Event Handling
const monitor = getMonitor();
monitor.on('metrics:collected', (event) => console.log('Metrics:', event.data));
monitor.on('alert:triggered', (event) => console.log('Alert!', event.data));Available events: metrics:collected · alert:triggered · alert:resolved · health:changed · database:slow-query · database:error
Prometheus / Grafana
scrape_configs:
- job_name: 'my-app'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/health/metrics'Alert Configuration
LINE Notify
alerts: { line: { token: 'YOUR_LINE_NOTIFY_TOKEN' } }Telegram
alerts: { telegram: { botToken: 'BOT_TOKEN', chatId: 'CHAT_ID' } }Email (SMTP)
alerts: { email: { host: 'smtp.gmail.com', port: 587, user: '...', password: '...', from: '...', to: ['admin@...'] } }API Reference
NestJS
import { VitalsPulseModule, NestPulseModule } from 'vitals-node';
// in AppModule.imports:
VitalsPulseModule.forRoot(options) // recommended
NestPulseModule.forRoot(options) // aliasExpress
import { expressPulse, createExpressRouter } from 'vitals-node/express';
app.use('/health', expressPulse(options)); // middleware
app.use('/health', createExpressRouter(options)); // router variantBun
import { bunPulse, withBunPulse } from 'vitals-node/bun';
const pulse = bunPulse(options);
// pulse.handler(req) — use in Bun.serve fetch
Bun.serve(withBunPulse({ ...options, fetch(req) { return new Response('OK'); } }));Core (Standalone)
import { createMonitor, getMonitor } from 'vitals-node/core';
const monitor = createMonitor(options);
await monitor.start();
// Metrics
const metrics = await monitor.getMetrics();
const health = await monitor.getHealth();
const detailed = await monitor.getDetailedHealth();
// Routes
monitor.discoverRoutes(app, 'express');
monitor.registerRoute({ method: 'GET', path: '/api/orders' });
monitor.annotateRoute('POST', '/api/orders', schema);
await monitor.analyzeRouteSchemas(schemaAnalysisConfig);
const registry = monitor.getRoutes();
// Registry push
const payload = monitor.buildRegistryPayload({ groupId, projectId, projectPath, tags, baseUrl });
await monitor.pushRegistryTo({ url: webhook, groupId, projectId, projectPath, tags, apiKey, baseUrl });
// Agent tools
monitor.registerAgentTool(tool);
monitor.registerAgentTools(tools);
monitor.getAgentToolSchemas();
await monitor.invokeAgentTool(name, input);
monitor.enableCallApi('http://localhost:3000'); // register call_api built-in tool
monitor.stop();Requirements
- Node.js 18+ or Bun 1.0+
- TypeScript 5+ (recommended)
License
MIT
