Web App · 2025
Snapz Dashboard
Snapz tracked every campaign in spreadsheets. We replaced that with a multi-tenant analytics platform: realtime event ingestion, role-based workspaces, Stripe billing and exportable reports — all behind a single sign-in.
Client
Snapz Media
Industry
SaaS / Analytics
Timeline
9 weeks
My role
Lead Full-Stack Developer
The problem
The team ran their entire operation from spreadsheets, so every report was a day old and permissions were impossible to manage.
The solution
A realtime dashboard with role-based access, live charts, billing and audit logs — replacing six spreadsheets and two manual reports.
-90%
Reporting time
4.2k
Daily events tracked
99.9%
Uptime since launch
What I built
Realtime charts
WebSocket stream pushes new events into the chart layer without a refetch.
Role-based access
Owner / admin / viewer roles enforced in Postgres row-level security, not just the UI.
Usage billing
Stripe metered subscriptions with in-app plan upgrades and invoice history.
Audit log
Every destructive action is written to an append-only table with actor and diff.
Architecture
- React + TypeScript SPA with TanStack Query for cache and optimistic updates
- Node/Fastify API, Zod-validated at every boundary
- PostgreSQL with RLS + TimescaleDB hypertable for event data
- Redis pub/sub fanning realtime updates to connected clients
- Deployed on Fly.io with GitHub Actions CI, preview envs per PR
Hard problems
4.2k events/min ingestion
Writes were batched into 1-second windows and flushed with COPY, dropping DB CPU from 80% to 12%.
Charts froze on large ranges
Server-side downsampling (LTTB) caps every series at 500 points, so a 1-year range renders as fast as a day.
Code from the build
A few real excerpts from the repository.
const buffer: EventRow[] = [];
export function queueEvent(row: EventRow) {
buffer.push(row);
if (buffer.length >= 500) void flush();
}
setInterval(flush, 1000);
async function flush() {
if (!buffer.length) return;
const batch = buffer.splice(0, buffer.length);
await db.copyFrom("events", batch); // ~30x faster than row inserts
redis.publish("events:new", JSON.stringify({ count: batch.length }));
}create policy "workspace members read events"
on public.events for select
to authenticated
using (
exists (
select 1 from public.memberships m
where m.workspace_id = events.workspace_id
and m.user_id = auth.uid()
)
);"We closed six spreadsheets on launch day. Reporting that took an afternoon now takes one click."
How it was built
- Discovery call, scope and fixed timeline agreed up front
- Custom design system built in Figma before development
- Typed, reviewed codebase with CI checks on every commit
- Deployed with monitoring, backups and post-launch support