@thanhlp18-qode/metrics-client
v0.2.1
Published
TypeScript/JavaScript client for metrics-ms — server-to-server (secret key) and browser (public key) ingestion in one package.
Maintainers
Readme
@thanhlp18-qode/metrics-client
TypeScript/JavaScript client for metrics-ms. Two clients in one package:
| Class | Use from | Endpoint | Key |
|---|---|---|---|
| MetricsClient | Server only (Express, Next.js route handlers / server actions, edge, Bun, Deno) | /api/v1/events, /api/v1/events/batch | METRICS_INGEST_API_KEY (secret) |
| MetricsBrowserClient | Browser (Next.js client components, vanilla JS) | /api/v1/events/public | METRICS_PUBLIC_API_KEY (public, ships in JS) |
Both clients are fail-safe: they never throw or reject. On any failure they log via the configured logger and return without breaking your flow.
Never use
MetricsClientin browser code — its key is a secret.MetricsBrowserClientis the only thing safe for client bundles.
Install
npm install @thanhlp18-qode/metrics-client
# or
pnpm add @thanhlp18-qode/metrics-clientServer: MetricsClient
import { MetricsClient } from '@thanhlp18-qode/metrics-client';
export const metrics = new MetricsClient({
baseUrl: process.env.METRICS_BASE_URL!,
apiKey: process.env.METRICS_INGEST_API_KEY!,
});
await metrics.track({
event_group_name: 'auth',
event_name: 'login_succeeded',
occurred_at: new Date(),
data: { user_id: 'u_123' },
});
await metrics.trackBatch(events); // auto-chunks at 500| Method | Returns |
|---|---|
| track(event) | Promise<IngestResponse \| null> — null on failure |
| trackBatch(events) | Promise<{ inserted: number }> — counts successful chunks only |
Browser: MetricsBrowserClient
import { MetricsBrowserClient } from '@thanhlp18-qode/metrics-client';
const metrics = new MetricsBrowserClient({
baseUrl: process.env.NEXT_PUBLIC_METRICS_BASE_URL!,
publicKey: process.env.NEXT_PUBLIC_METRICS_PUBLIC_KEY!,
// defaultEventGroupName: 'public_web', // default
});
metrics.track({ event_name: 'page_view' });
metrics.track({
event_name: 'cta_clicked',
data: { cta: 'pricing_hero' },
});What the browser client does for you:
- Stamps
occurred_at = new Date()if you don't pass one. - Defaults
event_group_nameto"public_web"(must match the server'sMETRICS_PUBLIC_ALLOWED_GROUPS). - Persists an
anonymous_idinlocalStorage(metrics_anon_id) and attaches it to every event. - Attaches
pathandreferrerfromlocation/document. - Uses
fetchwithkeepalive: trueso events fired during page unload still flush. - SSR-safe: in Node / RSC contexts where there is no
localStorage, enrichment is skipped instead of throwing.
User-supplied fields in data always win:
metrics.track({
event_name: 'page_view',
data: { path: '/custom-path', extra: 'wins-over-auto-enriched-fields' },
});Identity bridging
Once a user logs in, tie their browser history to a real user_id by
sending a server event from your backend that carries both ids:
// Server, after authenticating the user
await metrics.track({
event_group_name: 'identity',
event_name: 'user_identified',
occurred_at: new Date(),
data: {
user_id: session.userId,
anonymous_id: req.cookies.aid, // or read from a header
},
});To make the cookie available to your server, mirror the browser
anonymous_id into a cookie at app boot:
import { MetricsBrowserClient } from '@thanhlp18-qode/metrics-client';
const metrics = new MetricsBrowserClient({
baseUrl: process.env.NEXT_PUBLIC_METRICS_BASE_URL!,
publicKey: process.env.NEXT_PUBLIC_METRICS_PUBLIC_KEY!,
});
const aid = metrics.getAnonymousId();
if (aid) document.cookie = `aid=${aid}; path=/; SameSite=Lax`;metrics.setAnonymousId(id) is also exposed if you want to seed it from a
server-issued ID instead.
Express (server)
import express from 'express';
import { MetricsClient } from '@thanhlp18-qode/metrics-client';
const metrics = new MetricsClient({
baseUrl: process.env.METRICS_BASE_URL!,
apiKey: process.env.METRICS_INGEST_API_KEY!,
});
const app = express();
app.use(express.json());
app.post('/login', async (req, res) => {
// ... auth ...
await metrics.track({
event_group_name: 'auth',
event_name: 'login_succeeded',
occurred_at: new Date(),
data: {
user_id: req.body.user_id,
anonymous_id: req.cookies?.aid, // bridge from the browser
},
});
res.json({ ok: true }); // metrics outage cannot break this route
});Next.js
Server action (server-side)
'use server';
import { MetricsClient } from '@thanhlp18-qode/metrics-client';
const metrics = new MetricsClient({
baseUrl: process.env.METRICS_BASE_URL!,
apiKey: process.env.METRICS_INGEST_API_KEY!,
});
export async function recordSignup(formData: FormData) {
await metrics.track({
event_group_name: 'auth',
event_name: 'signup_completed',
occurred_at: new Date(),
data: { email_domain: String(formData.get('email')).split('@')[1] },
});
}Client component (browser-side)
'use client';
import { useEffect, useMemo } from 'react';
import { MetricsBrowserClient } from '@thanhlp18-qode/metrics-client';
export function PageViewTracker() {
const metrics = useMemo(
() =>
new MetricsBrowserClient({
baseUrl: process.env.NEXT_PUBLIC_METRICS_BASE_URL!,
publicKey: process.env.NEXT_PUBLIC_METRICS_PUBLIC_KEY!,
}),
[],
);
useEffect(() => {
metrics.track({ event_name: 'page_view' });
}, [metrics]);
return null;
}What goes where
Default to server. Use browser only for things only the browser can know.
| Tracking… | Use |
|---|---|
| Login, signup, payment, subscription change | Server |
| Page view, click, scroll, viewport, JS error | Browser |
| Authoritative user/org/payment IDs | Server |
| anonymous_id, path, referrer, browser perf | Browser |
| Anything containing PII or money | Server only |
Use event_group_name to namespace trust domains. The server's
METRICS_PUBLIC_ALLOWED_GROUPS enforces which groups the browser may write
to — typically just public_web.
Custom logger
import pino from 'pino';
const log = pino();
const metrics = new MetricsBrowserClient({
baseUrl: '...',
publicKey: '...',
logger: {
warn: (message, ctx) => log.warn({ ...ctx }, `metrics: ${message}`),
},
});The default logger uses console.warn.
Config reference
MetricsClient (server)
new MetricsClient({
baseUrl: 'https://metrics.example.com',
apiKey: '...',
timeoutMs: 5000, // default 5000
defaultHeaders: { 'x-app': 'api' }, // optional
fetch: customFetch, // optional
logger: customLogger, // default: console.warn
});MetricsBrowserClient
new MetricsBrowserClient({
baseUrl: 'https://metrics.example.com',
publicKey: '...',
defaultEventGroupName: 'public_web', // default
anonymousIdStorageKey: 'metrics_anon_id', // default
autoEnrich: true, // default
timeoutMs: 5000, // default
fetch: customFetch, // optional
logger: customLogger, // default: console.warn
});Build
npm install
npm run buildOutputs ESM + CJS + .d.ts to dist/.
