MiniObserv Integration Guide for Node.js Projects
This guide is for developers running Express.js, NestJS, or Next.js who want real visibility into what their app is doing in production — request latency, error rates, memory pressure, disk usage — without adding a heavy observability stack.
1. What MiniObserv actually covers
Before integrating, it's important to know which observability layer MiniObserv covers — and which it doesn't.
Layer 1 — Infrastructure ← MiniObserv
Is the machine healthy?
CPU, memory, disk, network, log lines
Tools: MiniObserv, Datadog, Prometheus
Layer 2 — Application (APM)
Is my code healthy?
Slow queries, N+1, stack traces, latency per function
Tools: Sentry, Datadog APM, OpenTelemetry
Layer 3 — Business
Is my product healthy?
Active users, conversions, feature usage
Tools: PostHog, Mixpanel, AmplitudeMiniObserv is Layer 1. It monitors the server running your app — the OS, not your code or your data. It does not connect to your PostgreSQL, read your Prisma schema, or know anything about your business logic. When you deploy to 3 servers, it tells you each server's CPU is at 42% — it won't tell you which Prisma query caused a spike (that's Layer 2).
In production you'll eventually want all three layers. Start with Layer 1 — if the server runs out of memory or disk, nothing else matters.
2. Why MiniObserv for Node.js projects
Your framework is solid. What you don't have is visibility into what happens once it's deployed.
What you get out of the box — zero code required:
- CPU, memory, disk, and network metrics collected every 10 seconds
- All your app's log lines surfaced in a structured dashboard log stream
- Alerts when system resources exceed thresholds you define
- Live sparkline dashboard at
http://localhost:8080
The MiniObserv agent is a standalone Go binary. It runs alongside your Node.js app as a separate container or process. It knows nothing about Node.js — it monitors the host OS. You don't need to install anything in your app to get system-level metrics.
The SDK (@kamerrezz/miniobserv) is optional. Use it only when you need to push custom application-level metrics (response time distributions, error counts, queue depths). Start without it — you may not need it.
2. Quick Setup (3 minutes)
Add MiniObserv to your existing docker-compose.yml. You need three new services alongside yours:
| Service | What it is | Touches your app? |
|---|---|---|
miniobserv-db | TimescaleDB — MiniObserv's own internal storage | No |
miniobserv | The MiniObserv server + dashboard | No |
miniobserv-agent | Collects OS metrics from the host | No |
miniobserv-dbis completely separate from your database. If your stack uses PostgreSQL + Prisma, MySQL + Sequelize, or anything else — MiniObserv never touches it. It has its own isolated database container. Your app and MiniObserv don't share any data storage.
Typical stack: Express + Prisma + PostgreSQL
This is what a real project looks like with MiniObserv added:
services:
# ── your existing stack ──────────────────────────────────────
db:
image: postgres:16
environment:
POSTGRES_USER: myapp
POSTGRES_PASSWORD: myapp
POSTGRES_DB: myapp_db
volumes:
- app_data:/var/lib/postgresql/data
app:
build: .
environment:
DATABASE_URL: "postgresql://myapp:myapp@db:5432/myapp_db"
depends_on:
- db
- miniobserv # optional: wait for dashboard to be ready
volumes:
- app_logs:/var/log/app
# ── MiniObserv — add these three services ───────────────────
miniobserv-db: # MiniObserv's own DB — isolated from yours
image: timescale/timescaledb:latest-pg16
environment:
POSTGRES_USER: minidog
POSTGRES_PASSWORD: minidog
POSTGRES_DB: miniobserv
volumes:
- miniobserv_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U minidog -d miniobserv"]
interval: 5s
retries: 10
miniobserv:
image: kamerrezz/miniobserv-server:latest
environment:
DATABASE_URL: "postgres://minidog:minidog@miniobserv-db:5432/miniobserv?sslmode=disable"
AGENT_TOKEN: "your-32-char-secret-here"
ALERT_RULES: '[{"host":"*","name":"cpu.usage_pct","op":">","threshold":80,"for":"5m"}]'
ports:
- "8080:8080"
depends_on:
miniobserv-db:
condition: service_healthy
miniobserv-agent:
image: kamerrezz/miniobserv-agent:latest
environment:
SERVER_URL: "http://miniobserv:8080"
AGENT_TOKEN: "your-32-char-secret-here"
LOG_PATHS: "/var/log/app/app.log"
volumes:
- app_logs:/var/log/app:ro # read-only access to your app's logs
volumes:
app_data: # your app's data
miniobserv_data: # MiniObserv's data — completely separate
app_logs: # shared between app (write) and agent (read)Generate a strong AGENT_TOKEN:
openssl rand -hex 32Use the same value in both miniobserv and miniobserv-agent. It never leaves your infrastructure.
Open http://localhost:8080 after docker compose up. The dashboard appears within ~10 seconds.
3. Express.js Integration
3a. Write logs to a file (so MiniObserv can tail them)
MiniObserv's agent watches files listed in LOG_PATHS and streams every new line into the dashboard log viewer. The simplest approach is to add a file transport to your existing Winston logger:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: '/var/log/myapp/app.log' }),
],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
});Mount the same volume in your app container so the agent can read the file:
services:
api:
volumes:
- app_logs:/var/log/myapp3b. Request logging middleware (structured format)
Log every request so MiniObserv's log stream shows meaningful HTTP activity. Log level is determined by status code — ERROR for 5xx, WARN for 4xx, INFO otherwise:
app.use((req, res, next) => {
res.on('finish', () => {
const level =
res.statusCode >= 500 ? 'error' :
res.statusCode >= 400 ? 'warn' : 'info';
logger[level]({
method: req.method,
path: req.path,
status: res.statusCode,
});
});
next();
});This is enough to see HTTP errors and slow routes in the dashboard without any SDK.
3c. Pushing custom app metrics with the SDK (optional)
Install the SDK:
npm install @kamerrezz/miniobservInitialize the client once and reuse it across your app:
import { MiniObservClient } from '@kamerrezz/miniobserv';
const obs = new MiniObservClient({
baseUrl: process.env.MINIOBSERV_URL ?? 'http://miniobserv:8080',
agentToken: process.env.AGENT_TOKEN,
defaultHost: process.env.HOSTNAME ?? 'api-server',
});Push a custom signal from middleware — fire-and-forget, never block the request:
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const heapUsed = process.memoryUsage().heapUsed;
const heapTotal = process.memoryUsage().heapTotal;
obs.pushMetric('mem.used_pct', (heapUsed / heapTotal) * 100)
.catch(() => {}); // never let observability crash the request
});
next();
});A note on metric names: MiniObserv has a fixed set of canonical names (cpu.usage_pct, mem.used_pct, disk.used_pct, etc.). The agent already populates these from the OS. When pushing from the SDK, use the same names to add data points from the app's perspective — or push any value you want to track. The recommended approach for most apps is to start with the sidecar pattern (agent only), and add SDK pushes only for high-value signals like error rate or DB latency.
4. NestJS Integration
4a. Global interceptor for request logging
Create an interceptor that logs every request with method, URL, status, and duration:
// src/common/observability.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class ObservabilityInterceptor implements NestInterceptor {
private readonly logger = new Logger('HTTP');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
const start = Date.now();
return next.handle().pipe(
tap(() => {
const res = context.switchToHttp().getResponse();
const duration = Date.now() - start;
this.logger.log(
`${req.method} ${req.url} → ${res.statusCode} (${duration}ms)`,
);
}),
);
}
}4b. Register globally in main.ts
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ObservabilityInterceptor } from './common/observability.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new ObservabilityInterceptor());
await app.listen(3000);
}
bootstrap();4c. Route NestJS logs to a file with Winston
npm install nest-winston winston// src/winston.config.ts
import * as winston from 'winston';
export const winstonConfig: winston.LoggerOptions = {
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: '/var/log/myapp/app.log' }),
],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
};// main.ts
import { WinstonModule } from 'nest-winston';
import { winstonConfig } from './winston.config';
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger(winstonConfig),
});With this in place, every NestJS log — including the interceptor output — lands in /var/log/myapp/app.log, which the MiniObserv agent tails automatically.
5. Next.js Integration
5a. API route middleware (App Router, Next.js 14+)
Next.js middleware runs at the Edge and does not have access to Node.js file APIs. The simplest way to get logs into MiniObserv is to write structured JSON to stdout and redirect Docker's output to a file.
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const start = Date.now();
const response = NextResponse.next();
console.log(
JSON.stringify({
level: 'INFO',
method: request.method,
path: request.nextUrl.pathname,
duration_ms: Date.now() - start,
}),
);
return response;
}
export const config = {
matcher: '/api/:path*',
};5b. Redirect Next.js stdout to a file (docker-compose)
Pipe the container's stdout to a file that the agent can tail:
services:
nextjs:
command: sh -c "node server.js 2>&1 | tee /var/log/myapp/app.log"
volumes:
- app_logs:/var/log/myappThe tee command writes to both stdout (so you can still docker logs) and the file. The MiniObserv agent picks up new lines automatically via LOG_PATHS.
6. Alert Rules for Node.js Apps
Define alert rules as a JSON array in the ALERT_RULES environment variable on the MiniObserv server. Each rule fires when the condition holds for the specified duration:
[
{ "host": "*", "name": "cpu.usage_pct", "op": ">", "threshold": 80, "for": "5m" },
{ "host": "*", "name": "mem.used_pct", "op": ">", "threshold": 85, "for": "10m" },
{ "host": "*", "name": "disk.used_pct", "op": ">", "threshold": 90, "for": "1m" }
]host: "*"matches all hosts reporting to this serverforis the minimum sustained duration before the alert fires — avoids noise from transient spikes- Fired alerts appear in the dashboard; configure
ALERT_NOTIFICATIONSto also receive webhook notifications (see Section 8)
A practical starting point for a typical Node.js API:
| Metric | Threshold | For | Rationale |
|---|---|---|---|
cpu.usage_pct | 80% | 5m | Sustained load — not a single spike |
mem.used_pct | 85% | 10m | Memory leaks grow slowly |
disk.used_pct | 90% | 1m | Disk full is immediate and fatal |
8. Notifications and Host Health
8a. Webhook alert notifications
MiniObserv can notify external services when an alert fires or resolves. Set ALERT_NOTIFICATIONS on the server to a JSON array of webhook destinations:
miniobserv:
environment:
ALERT_NOTIFICATIONS: '[{"type":"webhook","url":"https://hooks.slack.com/services/YOUR/WEBHOOK/URL"}]'Works with Slack, Discord, Teams, PagerDuty, or any service that accepts a JSON POST. Each notification is delivered with a 5-second timeout (fire-and-forget, no retry in v1).
Payload shape:
{"event":"firing","rule":{"host":"*","name":"mem.used_pct","op":">","threshold":85,"for":"10m"},"value":87.4,"fired_at":"2026-06-05T16:42:23Z"}event is "firing" when the threshold is crossed and "resolved" when the metric drops back below it.
8b. Automatic host-down alerts
The server tracks the last heartbeat for each connected agent. If an agent stops reporting metrics — due to a container crash, OOM kill, network partition, or any other reason — MiniObserv will automatically fire a host.down webhook once the host has been silent for longer than HOST_DOWN_AFTER (default 50s).
No configuration is required beyond setting ALERT_NOTIFICATIONS. You do not need to write an alert rule for this — host health is tracked automatically.
| Env var | Default | Meaning |
|---|---|---|
HOST_STALE_AFTER | 20s | Host becomes orange (stale) in the dashboard |
HOST_DOWN_AFTER | 50s | Host becomes red (down) and webhook fires |
You can query the current status of all hosts via GET /api/v1/hosts (public, no auth):
curl -s http://localhost:8080/api/v1/hosts | jq .8c. Updated docker-compose snippet with notifications
miniobserv:
build:
context: ./path/to/theminidog
dockerfile: Dockerfile.server
environment:
DATABASE_URL: "postgres://minidog:minidog@miniobserv-db:5432/miniobserv?sslmode=disable"
AGENT_TOKEN: "your-32-char-secret-here"
ALERT_RULES: '[{"host":"*","name":"cpu.usage_pct","op":">","threshold":80,"for":"5m"},{"host":"*","name":"mem.used_pct","op":">","threshold":85,"for":"10m"}]'
ALERT_NOTIFICATIONS: '[{"type":"webhook","url":"https://hooks.slack.com/services/YOUR/WEBHOOK/URL"}]'
HOST_STALE_AFTER: "20s"
HOST_DOWN_AFTER: "50s"
ports:
- "8080:8080"
depends_on:
miniobserv-db:
condition: service_healthy7. What You Get Without Writing a Single Line of SDK Code
If you only add the two MiniObserv services to your docker-compose and point LOG_PATHS at your app's log file, you get:
- System metrics — CPU, memory, disk, network collected every 10 seconds, automatically
- Log stream — every line your app writes to the log file, visible in the dashboard with timestamps
- Alerts — notifications when resources cross the thresholds you define
- Live dashboard — sparkline charts at
http://localhost:8080, no setup required
The SDK is for when you need to go further: track error rates by route, measure DB query latency, record queue depths. Start without it. Add it when you have a specific signal you cannot observe from logs and system metrics alone.
Reference
Canonical metric names
| Name | Unit | Description |
|---|---|---|
cpu.usage_pct | % | CPU utilization across all cores |
mem.used_pct | % | Memory used as a percentage of total |
mem.used_bytes | bytes | Memory currently in use |
mem.total_bytes | bytes | Total installed memory |
disk.used_pct | % | Disk used percentage (mount /) |
disk.used_bytes | bytes | Disk space in use |
disk.total_bytes | bytes | Total disk capacity |
net.bytes_in | bytes | Network bytes received (delta per tick) |
net.bytes_out | bytes | Network bytes sent (delta per tick) |
net.*metrics are delta values — no data is emitted on the first collection tick.
Agent environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
SERVER_URL | yes | — | Base URL of the MiniObserv server |
AGENT_TOKEN | yes | — | Shared HS256 secret (min 16 chars) |
COLLECT_INTERVAL | no | 10s | Collection frequency (1s–300s) |
LOG_PATHS | no | — | Comma-separated paths to tail |
AGENT_HOST | no | OS hostname | Host label on all metrics |