WatchTower is a lightweight observability platform for web applications. A small browser SDK embedded in a monitored app captures JavaScript errors, user interactions, and performance metrics and streams them to a Node.js backend, which persists them to Postgres (Supabase) and renders them in a real-time, per-user dashboard. It is built by CSE 110 Team 09 as a course project, but it is structured and documented to run as a real, deployable product.
Modern web teams need to know what is actually happening in production: which errors users hit, how fast pages load, which routes are slow, and what users do. WatchTower provides that visibility without a heavy agent or vendor lock-in. Drop the SDK into any web page, point it at a WatchTower backend, and the dashboard fills with live error feeds, latency charts, active-user counts, and deploy-version breakdowns. Dashboard access is authenticated with Clerk and data is scoped per user.
Demo GIF/Screenshot: TODO - add short GIF or screenshot before final submission if available.
External monitored app
│ (embeds src/sdk/watchtower.js)
▼
WatchTower SDK ──POST /api/events──► Node.js backend (Render)
│ src/backend/server.js
▼
Supabase / Postgres
│
▼
WatchTower dashboard (Clerk-authenticated)
DEFAULT_INGEST_OWNER_USER_ID is a temporary prototype mapping: because the external test app has no Clerk session, its otherwise-anonymous events are attributed to one demo owner so they appear on a dashboard. The long-term replacement is a per-app/project key ingestion model so each monitored app maps to the correct owner without a human login.See docs/architecture/auth-workflow.md for the full authentication and data-scoping flow.
.github/ # Issue templates, Dependabot, and CI workflows
archive/ # Historical Prototype 1 & 2 code (kept for project history)
docs/ # Product, architecture, ADRs, process, research, sprint docs
scripts/ # Build/startup helpers (e.g. Clerk config generation)
src/
├── backend/ # Node.js HTTP server, event store, mailer, alert logic
├── frontend/ # dashboard/, landing/, auth/, demo/, dashboard-demo/, assets/
├── sdk/ # Browser SDK (watchtower.js)
└── shared/ # Shared utilities (event-utils.js)
tests/ # unit/ (Jest) and e2e/ (Playwright)
See src/README.md for the source layout and docs/README.md for the documentation index.
Prerequisites: Node.js 18 or later and npm.
Clone the repository
git clone https://github.com/cse110-sp26-group09/Watchtower-Course-Project.git
cd Watchtower-Course-Project
Install dependencies
npm install
Create your environment file
cp .env.example .env
Configure Clerk / Supabase / alerts as needed (all optional for a basic local run; see Environment Variables). Without Supabase the server uses in-memory storage; without a real Clerk key it runs in prototype/header-trust mode so tests and local development work.
Start the server
npm start
The start script runs npm run config:clerk to generate src/frontend/auth/clerk-config.js from CLERK_PUBLISHABLE_KEY, then boots src/backend/server.js.
Open the app
| URL | Description |
|---|---|
http://localhost:3000/ |
Redirects to the landing page |
http://localhost:3000/landing/ |
Public landing page |
http://localhost:3000/dashboard |
Dashboard (requires Clerk sign-in) |
http://localhost:3000/demo/ |
Monitored demo app that sends events |
http://localhost:3000/dashboard-demo/ |
Static dashboard preview (no sign-in) |
All configuration is via environment variables. Copy .env.example to .env and fill in the values you need; it documents every variable with comments. Highlights:
| Variable | Required | Description |
|---|---|---|
CLERK_PUBLISHABLE_KEY |
For real auth | Clerk publishable key (pk_test_... / pk_live_...). |
SUPABASE_URL |
For persistence | Supabase project URL. |
SUPABASE_SERVICE_ROLE_KEY |
For persistence | Supabase service-role key. |
SUPABASE_ANON_KEY |
Optional | Supabase anon key (fallback when no service-role key). |
SUPABASE_P3_EVENTS_TABLE |
No | Events table name (default prototype3_events). |
DEFAULT_INGEST_OWNER_USER_ID |
No | Temporary demo-only owner for unauthenticated external events. Replace with project/app keys for production. |
GMAIL_ADDRESS / GMAIL_CLIENT_ID / GMAIL_CLIENT_SECRET / GMAIL_REFRESH_TOKEN |
For alerts | Gmail OAuth credentials for threshold alert emails. |
PORT |
No | Server port (Render sets this automatically; default 3000). |
Never commit
.envor a generatedclerk-config.jscontaining a real key. Only.env.exampleandclerk-config.example.jscarry placeholders.
If using Supabase, run the SQL in docs/architecture/auth-workflow.md (or the snippet below) once in the Supabase SQL editor to create the prototype3_events and app_users tables and grant the service role access:
create table if not exists public.prototype3_events (
id text primary key,
type text not null,
event_name text,
timestamp timestamptz not null,
session_id text,
user_id text,
route text,
deploy_version text,
app_name text,
environment text,
sdk_version text,
data jsonb default '{}'::jsonb,
received_at timestamptz not null
);
create index if not exists idx_prototype3_events_type on public.prototype3_events(type);
create index if not exists idx_prototype3_events_user_received_at on public.prototype3_events(user_id, received_at);
create table if not exists public.app_users (
clerk_user_id text primary key,
email text not null default '',
display_name text not null default '',
timezone text not null default '',
last_seen_at timestamptz not null
);
grant usage on schema public to service_role;
grant select, insert, update, delete on public.prototype3_events to service_role;
grant select, insert, update, delete on public.app_users to service_role;
npm run test:unit # Jest unit tests for src/backend pure modules (no server needed)
npm run test:e2e # Playwright end-to-end tests (start the server first; see below)
npm run docs:js # Generate JSDoc API docs into docs/api/ (gitignored)
The end-to-end tests target a running server. Start it in one terminal (npm start) and run npm run test:e2e in another, or set BASE_URL to point at a different host. CI starts the server in the background automatically; see .github/workflows/ci.yml, which also runs HTML/CSS/JS validation and a dependency audit. More detail in tests/README.md.
npm start, which generates clerk-config.js from CLERK_PUBLISHABLE_KEY and boots src/backend/server.js. Set CLERK_PUBLISHABLE_KEY, the SUPABASE_* variables, and any alert/DEFAULT_INGEST_OWNER_USER_ID values under Render → Environment.prototype3_events and app_users tables (see above). The backend uses the service-role key server-side only./api/events endpoint. GitHub Pages serves static files only and runs neither the backend nor the database.CLERK_PUBLISHABLE_KEY to the backend environment; the publishable key is the only Clerk value exposed to the browser.Start at the documentation index: docs/README.md.
docs/architecture/ (system overview, API contracts, event schemas, auth workflow)docs/adr/ (Architecture Decision Records)docs/process/ (workflow, git workflow, JSDoc standards, code-review feedback)docs/product/, docs/planning/ (sprint plans + retrospectives)npm run docs:js to produce docs/api/ (gitignored)SECURITY.mdDEFAULT_INGEST_OWNER_USER_ID hard-maps all unauthenticated external events to one owner. This should be replaced with a multi-tenant project/app key ingestion model.archive/ for history only; they are not part of the active product.sub claim) is recommended before broad production use.WatchTower is the CSE 110 (Spring 2026) Team 09 course project. Roles:
| Role | Member |
|---|---|
| Technical Lead / CI-CD / Architecture | Aditya |
| Product / Process / Sprint Documentation Lead | Fahad |
| Frontend Lead | James |
| UI/UX Lead | Hieu |
| Instrumentation / Backend Prototype Lead | Daniel |
| JavaScript Instrumentation Owner | Jason |
| Data / Backend Logic Owner | Waleed |
| Documentation / Communication / Requirements Support | Josh |
| Research / QA / AI Tools Support | Woosik |
| Frontend Prototype Support | Alex |
| Frontend Components / Styling Support | Hemendra |
The video should cover:
docs/adr/.