Full-stack · 2026

DevPulse

Self-hosted product analytics: ingestion API, SQLite engine, React dashboard

Complete · source available

Overview

  • What: self-hosted product analytics in one TypeScript monorepo, installed with a single npm install and no external services.
  • Hardest problem: defining aggregations, funnels and cohorts precisely, so any dashboard number traces back through the SQL to the events behind it.
  • How it is validated: a vitest suite, strict TypeScript, ESLint with warnings as errors, bundle-size budgets and CI.

It is small enough to read end to end. It includes a typed ~2 kB browser SDK and production operations features such as key rotation, rate limiting, retention sweeps and Prometheus metrics.

Architecture

 browser SDK (~2 kB) --POST /ingest--> API (Express) --> SQLite (better-sqlite3)
 pixel fallback ------GET----------->    |                    |
                                         |  aggregation engine (SQL): metrics, funnels, cohorts
                                         v                    |
                                  React dashboard <-----------+
 packages: shared <- sdk, api, web (one-directional)
fig. 01 — DevPulse architecture

Highlights

  • Batched POST /ingest with per-event validation and partial-success reporting (202 whole batch stored, 207 with offending indices), plus a sendBeacon-friendly pixel fallback.
  • Aggregations (visitors, events, sessions, bounce rate, session length) bucketed by hour/day/week/month with zero-padded series and period-over-period deltas; breakdowns by page, referrer host, country, device, browser and OS.
  • Ordered, windowed funnels and cohort retention triangles (a visitor belongs to the cohort of their first-ever visit, so widening the range never reshuffles cohorts); bot traffic stored but excluded by default.
  • Dashboard with a hand-rolled SVG chart, light/dark theming and keyboard navigation, with no chart library and no CSS framework.
  • Operations: hashed write keys with rotation, JWT auth with rotating refresh tokens, role-based team admin, per-project rate limiting, retention sweeps, streaming CSV/NDJSON export, Prometheus /metrics, /health.
  • Four packages with a one-directional dependency rule (shared <- sdk, api, web); GitHub Actions CI, vitest suite, ESLint with warnings as errors, bundle-size budgets, six docs.

What I learned

Defining cohorts by a visitor’s first-ever visit means widening the date range never reshuffles them, which is the kind of rule worth deciding up front. Keeping the dependency direction between the four packages one-way kept the monorepo easy to read.