New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@litemetrics/node

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@litemetrics/node

Node.js analytics collector with DB adapters and query API

latest
Source
npmnpm
Version
0.9.0
Version published
Weekly downloads
1
-75%
Maintainers
1
Weekly downloads
 
Created
Source

@litemetrics/node

Self-hosted analytics server for Litemetrics. Includes event collection, query API, site management, GeoIP enrichment, and bot filtering.

Installation

npm install @litemetrics/node

Quick Start

import express from 'express';
import { createCollector } from '@litemetrics/node';

const app = express();
app.use(express.json());

const collector = await createCollector({
  db: { url: 'http://localhost:8123' }, // ClickHouse (default)
  adminSecret: 'your-admin-secret',
  geoip: true,
  cors: { origins: [] }, // Allow all origins
});

// Event collection endpoint (receives tracker data)
app.post('/api/collect', collector.handler());

// Query API (stats, time series, retention)
app.get('/api/stats', collector.queryHandler());

// Event & user listing
app.all('/api/events', collector.eventsHandler());
app.all('/api/users/*', collector.usersHandler());

// Site management (CRUD)
app.all('/api/sites/*', collector.sitesHandler());

// Serve tracker script
app.use(express.static('node_modules/@litemetrics/tracker/dist'));

app.listen(3000);

Database Adapters

ClickHouse (Default)

const collector = await createCollector({
  db: { url: 'http://localhost:8123' },
});

Uses MergeTree for events and ReplacingMergeTree for sites. Tables are auto-created on init.

Postgres

const collector = await createCollector({
  db: { adapter: 'postgres', url: 'postgres://user:pass@localhost:5432/litemetrics' },
});

Full feature parity with the ClickHouse adapter — every metric, time series, top-N query, and retention cohort produces identical results across both backends. Uses native jsonb for properties/traits, indexed (site_id, timestamp) for fast range scans, and a soft-delete column on sites. Tables are auto-created on init. Recommended when you already run Postgres and want one less moving piece.

MongoDB

const collector = await createCollector({
  db: { adapter: 'mongodb', url: 'mongodb://localhost:27017/litemetrics' },
});

Ad Click IDs

Every adapter stores five nullable ad-attribution columns alongside the UTM ones: gclid, gbraid, wbraid, fbclid and fbp. The tracker captures the four click IDs from the landing URL and keeps them for 90 days, so a conversion event fired days later still carries the click that paid for it - which is what Google Ads and Meta conversion upload APIs key on. UTM values are not an accepted substitute there.

Existing deployments upgrade themselves: ClickHouse and Postgres run an idempotent ADD COLUMN IF NOT EXISTS on init(), and MongoDB is schemaless. Restarting the collector is enough - there is no manual SQL and no backfill. Rows written before the upgrade keep NULL in all five columns.

Timestamp Sanitization

The collector validates client-supplied event timestamps against server time. Events with timestamps outside the allowed window are dropped by default, preventing data corruption from clients with incorrect system clocks and timestamp-spoofing analytics poisoning.

Defaults: 5 min future tolerance, 24 h past tolerance, mode: 'drop'.

const collector = await createCollector({
  db: { url: 'http://localhost:8123' },
  timestampSanity: {
    // pastMs: 7 * 24 * 60 * 60 * 1000, // widen for long offline queues
    // mode: 'clamp',                    // replace with server-now instead of dropping
    onOutOfWindow: ({ reason, offsetMs, event }) => {
      metrics.increment('litemetrics.ts_out_of_window', { reason });
    },
  },
});

Modes:

  • 'drop' (default) — out-of-window and invalid (NaN, missing, non-number) events are silently discarded.
  • 'clamp' — replace the timestamp with server-now instead of dropping. Preserves the event body.
  • 'off' — pass valid client timestamps through unchanged. Invalid values (NaN, missing, non-number) are still replaced with server-now to avoid breaking downstream storage.

If your tracker uses an offline queue that may replay events after long delays, widen pastMs accordingly (e.g., 7 * 24 * 60 * 60 * 1000 for a 7-day queue).

The optional onOutOfWindow(info) callback fires whenever the sanitizer rejects a value (drop or clamp). Use it to wire poisoning signals into your metrics/alerting. info.reason is 'future' | 'past' | 'invalid'; info.offsetMs is how far past the boundary the value was (0 for invalid types). Callback errors are swallowed.

Bot Filtering

Multi-layer filtering runs on every collected event of a web site. Defaults are conservative (legitimate traffic is never dropped on a false-positive heuristic) and tunable per-site via the botFilterMode site field.

  • Layer 1 (signature) — UA matched against the isbot list.
  • Layer 2 (heuristic) — scrubbed / empty UAs (no UA, bare Mozilla/5.0, missing platform tokens).
  • Layer 3 (rate limit) — sliding-window per-IP cap.

Sites with type: 'app' run Layer 3 only. Layers 1 and 2 reason about browser User-Agents, which an app SDK does not send (React Native on Android goes out as okhttp/<version>, which isbot matches), so on an app site they would only misfire. A site receiving app SDK traffic must therefore be typed app (POST / PUT /api/sites with {"type":"app"}); otherwise it keeps being filtered as browser traffic and loses its Android events. When app SDK payloads arrive at a non-app site the collector fires onSiteTypeMismatch once per site (reporting only — the payload never bypasses the filter).

Modes: off, standard (default — Layer 1 drops, Layers 2 & 3 flag; on an app site nothing runs), strict (every layer drops; app site: rate limit only), shadow (every layer flags only). Every detection reports both the layer that fired and a finer reason — the signature layer fires for a missing User-Agent (empty-ua) and for an isbot list match (ua-signature), and those call for different responses from an operator. Pass per-collector via botFilter:

const collector = await createCollector({
  db: { url: 'http://localhost:8123' },
  botFilter: {
    defaultMode: 'standard',     // server-wide default (off | standard | strict | shadow)
    rateLimitWindowMs: 60_000,   // sliding window for Layer 3
    rateLimitMaxEvents: 60,      // max events / window / IP
    onBotDetected: (info) => {
      // info: { siteId, ip, userAgent, layer, reason, action, mode }
      // reason: 'empty-ua' | 'ua-signature' | 'no-browser-signals' | 'rate-limit'
      console.log(`[bot-filter] ${info.action} layer=${info.layer} reason=${info.reason} mode=${info.mode} site=${info.siteId} ip=${info.ip} ua="${info.userAgent}"`);
    },
    onSiteTypeMismatch: (info) => {
      // info: { siteId, siteType, platform, mode } — fired once per site when app SDK
      // events arrive at a site whose type is not 'app'. Fix: PUT /api/sites/:id {"type":"app"}
      console.warn(`[site-type-mismatch] site=${info.siteId} type=${info.siteType ?? 'unset'} platform=${info.platform} mode=${info.mode}`);
    },
  },
});

Server wrapper env vars (apps/server):

  • BOT_FILTER_MODE (default standard): one of off / standard / strict / shadow. Controls server-wide bot filtering for sites that don't override per-site.
  • BOT_RATE_WINDOW_MS (default 60000): sliding-window size for the per-IP rate limiter (ms).
  • BOT_RATE_MAX (default 60): max events per window per IP before the rate-limit layer fires.
  • BOT_LOG_MAX_PER_MIN (default 20): detail [bot-filter] log lines allowed per minute; the overflow is counted as suppressed= on the [collect] summary line.

Read endpoints (/api/stats, /api/events, /api/users) exclude flagged events by default. Pass ?includeBots=true to include them.

GDPR / Right to erasure

const { deleted } = await db.deleteUserEvents('site_abc', 'user_123');
// or via HTTP:
//   DELETE /api/users/:identifier/events?siteId=site_abc
//   Headers: X-Litemetrics-Admin-Secret OR X-Litemetrics-Secret (matching site)
//   Returns: { ok: true, deleted: <number> }

Resolves both userId and visitorId through the identity map; idempotent.

Features

  • Event Collection - Receives batched events from the browser tracker
  • Bot Filtering - Multi-layer (signature + heuristic + rate limit), per-site mode
  • GeoIP Enrichment - Resolves country/city from IP using MaxMind GeoLite2
  • User-Agent Parsing - Extracts browser, OS, and device type
  • Hostname Filtering - Only count events from allowed hostnames per site (matched against request Origin/Referer)
  • Query API - Built-in metrics for pages, events, conversions, and behavioral insights
  • Segmentation Filters - Filter any metric/time series by geo, device, UTM, referrer, or event metadata
  • Time Series - Hourly/daily/weekly/monthly breakdowns
  • Retention Analysis - Weekly cohort retention
  • Site Management - Multi-tenant CRUD with secret key auth
  • Server-Side Tracking - Track events and identify users from your backend

Server-Side Tracking

// Track events from your backend
await collector.track('site-id', 'Purchase', { amount: 99 }, { userId: 'user-123' });

// Identify users
await collector.identify('site-id', 'user-123', { plan: 'pro' });

Identity Merging

Litemetrics automatically links anonymous visitors to identified users. After a user is identified, subsequent events are merged across all linked visitor IDs. GET /api/users/:identifier accepts either a visitorId or a userId and returns a merged profile when available.

Backfill (one-time)

If you already have identify events, run the backfill script once to populate the identity map:

bun packages/node/src/backfill-identity.ts --adapter clickhouse --url http://localhost:8123

Metrics

MetricDescription
pageviewsTotal page views
visitorsUnique visitors
sessionsUnique sessions
eventsCustom events count
conversionsConversion events count
top_pagesMost visited pages
top_referrersTop traffic sources
top_countriesVisitors by country
top_citiesVisitors by city
top_eventsMost common custom events
top_conversionsMost common conversion events
top_exit_pagesLast page in session
top_transitionsPage-to-page transitions
top_scroll_pagesPages with most scroll depth events
top_button_clicksMost clicked buttons (auto events)
top_link_targetsMost clicked link targets
top_browsersBrowser breakdown
top_osOS breakdown
top_devicesDevice type breakdown

License

MIT

Keywords

analytics

FAQs

Package last updated on 03 Sep 2026

Related posts