Serverless Monitoring and Incident Status Platform
PulseOps is a portfolio-grade uptime monitoring platform built with React, TypeScript, Cloudflare Workers, Cloudflare D1, Worker Cron Triggers, and Discord webhooks. It lets users create monitors, run checks manually or on a schedule, track latency, automate incidents, and publish public status pages.
- Frontend: https://pulseops-a0u.pages.dev
- Backend health check: https://pulseops-api.pulseops-gauravpsingh.workers.dev/api/health
- GitHub: https://github.com/gauravpsingh07/pulseops
PulseOps is deployed on a free-tier Cloudflare architecture:
- Frontend: Cloudflare Pages
- Backend API: Cloudflare Workers
- Database: Cloudflare D1
- Scheduler: Cloudflare Cron Triggers
- Alerts: Discord Webhooks
- CI/CD: GitHub Actions
The deployed app supports user registration, login, monitor creation, manual checks, scheduled checks, incident detection, Discord outage/recovery alerts, and public status pages.
- Register or log in.
- Create a website/API monitor.
- Run a manual check or wait for scheduled checks.
- View uptime, response time, check history, and incidents.
- Add a Discord webhook to receive outage and recovery alerts.
- Enable public status and open
/status/:slugin an incognito browser.
PulseOps monitors websites and APIs from a Cloudflare Worker. Authenticated users can manage monitors, inspect uptime metrics, view recent check history, configure Discord alerts, and expose a public status page for selected services.
Small projects and portfolio apps often need visibility into uptime but do not need expensive infrastructure. Typical monitoring tools can be overkill, while basic cron pings do not provide incident history, alert deduplication, response-time metrics, or public status communication.
PulseOps provides a lightweight monitoring system on a free-tier-friendly serverless architecture:
- Cloudflare Workers run the API and monitoring engine.
- Cloudflare D1 stores users, monitors, checks, incidents, alert logs, and rate limits.
- Cloudflare Cron Triggers schedule checks.
- Discord webhooks notify teams when incidents open or resolve.
- Cloudflare Pages can host the React dashboard and public status pages.
- Email/password authentication with hashed passwords and JWT sessions.
- Monitor CRUD for website and API endpoints.
- Heartbeat (dead-man-switch) monitors: cron jobs ping a secret URL, and a missed ping past the interval + grace window records a failure and opens incidents through the normal pipeline.
- Manual uptime checks from the dashboard.
- Scheduled checks through Cloudflare Cron Triggers.
- 90-day uptime history bars backed by per-day rollups (
daily_stats), shown on monitor detail and public status pages. - Combined status board: one public page per user showing all public monitors with an overall system banner.
- Response-time tracking and metrics cards.
- Recharts response-time charts.
- Incident automation with a 3-failure outage threshold.
- Incident deduplication and automatic recovery resolution.
- Discord webhook alerts and alert log persistence.
- Public status pages for selected monitors.
- Embeddable live SVG status badges (
/api/status/:slug/badge.svg) for READMEs and docs. - D1-backed rate limiting for auth, monitor creation, manual checks, and cron fallback.
- CORS, security headers, Zod validation, and clean error responses.
- GitHub Actions CI for install, lint, typecheck, tests, and build.
User Browser
-> Cloudflare Pages React App
-> Cloudflare Worker API
-> Cloudflare D1
Scheduled monitoring flow:
Cloudflare Cron Trigger
-> scheduled()
-> monitor runner
-> checks table
-> incident service
-> Discord alert service
See docs/architecture.md for more detail.
- Frontend: Vite, React, TypeScript, Tailwind CSS, React Router, Recharts
- Backend: Cloudflare Workers, TypeScript
- Database: Cloudflare D1
- Scheduler: Cloudflare Worker Cron Triggers
- Alerts: Discord webhooks
- Validation: Zod
- Tests: Vitest
- Package manager: pnpm workspaces
- CI: GitHub Actions
PulseOps separates the system into a React web app and a Worker API:
apps/web: authenticated dashboard, monitor detail pages, and public status UI.worker: API routes, auth, D1 queries, monitor runner, incident logic, alerts, rate limiting, and cron entrypoint.worker/src/db/schema.sql: D1 schema..github/workflows/ci.yml: CI workflow.
The frontend uses a Vite dev proxy for local /api calls. In production, set VITE_API_BASE_URL to the deployed Worker URL if the frontend and API are on different origins.
Main D1 tables:
users: account records, password hashes, and the public status-board slug.monitors: endpoint configuration, monitor type (httporheartbeat), heartbeat token/grace, current status, counters, public slug, and optional alert webhook.checks: individual check results with status code, response time, error, and timestamp.daily_stats: per-monitor per-day rollups powering the 90-day uptime bars.incidents: open and resolved outage records.alert_logs: Discord alert delivery attempts, skipped alerts, and failures.rate_limits: D1-backed rate-limit counters.
Schema source: worker/src/db/schema.sql
Existing deployments upgrade with the incremental migration (creates daily_stats, backfills ~30 days of rollups from retained checks, and adds the board and heartbeat columns):
corepack pnpm --filter worker exec wrangler d1 execute pulseops-db --remote --file=src/db/migrations/002_features.sqlRun it once; the ALTER TABLE statements are not re-runnable.
See docs/api.md.
Route groups:
POST /api/auth/registerPOST /api/auth/loginGET /api/auth/meGET /api/monitorsPOST /api/monitorsGET /api/monitors/:idPATCH /api/monitors/:idDELETE /api/monitors/:idPOST /api/monitors/:id/checkGET /api/monitors/:id/checksGET /api/monitors/:id/metricsGET /api/monitors/:id/incidentsGET /api/incidentsPATCH /api/incidents/:id/resolveGET /api/status/:slugGET /api/status/:slug/metricsGET /api/status/:slug/incidentsGET /api/status/:slug/badge.svgGET /api/boardandPOST /api/board(authenticated board slug management)GET /api/board/:slug(public combined status board)GET|POST /api/ping/:token(heartbeat pings)POST /api/cron/check-monitors
Summary:
- HTTP
2xxand3xxresponses count as success. - Other HTTP responses, network errors, and timeouts count as failure.
- A successful check marks the monitor
operational, resetsfailure_count, and incrementssuccess_count. - Failed checks increment
failure_count. - Failures 1 and 2 mark the monitor
degraded. - Failure 3 and later mark the monitor
down. - The first down transition opens one incident.
- Duplicate open incidents are not created.
- Recovery resolves the open incident and can send a Discord resolution alert.
- Heartbeat monitors invert the flow: pings record successful checks, and the scheduler records a failed check when a ping is overdue (interval + grace). A never-pinged heartbeat stays pending instead of alerting.
- Scheduled check retention deletes checks older than 30 days, rate-limit counters older than 24 hours, and daily rollups older than 90 days.
Install dependencies:
corepack enable
corepack pnpm installCreate local Worker secrets:
Copy-Item .env.example worker/.dev.varsEdit worker/.dev.vars:
JWT_SECRET=replace-with-long-random-secret
CRON_SECRET=replace-with-long-random-secret
Apply the D1 schema locally:
corepack pnpm --filter worker exec wrangler d1 execute pulseops-db --local --file=src/db/schema.sqlRun both the Worker and web app:
corepack pnpm devOr run them separately:
corepack pnpm --filter worker dev -- --port 8787
corepack pnpm dev:webOpen the Vite URL, usually http://localhost:5173.
Create a D1 database:
corepack pnpm --filter worker exec wrangler d1 create pulseops-dbCopy the returned database_id into worker/wrangler.toml:
[[d1_databases]]
binding = "DB"
database_name = "pulseops-db"
database_id = "YOUR_DATABASE_ID"Apply the schema locally or remotely:
corepack pnpm --filter worker exec wrangler d1 execute pulseops-db --local --file=src/db/schema.sql
corepack pnpm --filter worker exec wrangler d1 execute pulseops-db --remote --file=src/db/schema.sqlSet Worker secrets:
corepack pnpm --filter worker exec wrangler secret put JWT_SECRET
corepack pnpm --filter worker exec wrangler secret put CRON_SECRETDeploy:
corepack pnpm --filter worker deployDeploy apps/web to Cloudflare Pages.
Recommended Pages settings:
- Build command:
corepack pnpm --filter web build - Build output directory:
apps/web/dist - Root directory: repository root
- Environment variable:
VITE_API_BASE_URL=https://YOUR_WORKER_URL
If the frontend and Worker are on different origins, keep Worker CORS configured with the deployed frontend origin.
worker/wrangler.toml includes:
[triggers]
crons = ["*/5 * * * *"]Cloudflare invokes the Worker's scheduled() handler. For local fallback testing:
curl.exe -X POST http://localhost:8787/api/cron/check-monitors -H "x-cron-secret: YOUR_CRON_SECRET"PulseOps is designed for a free-tier-friendly architecture:
- Cloudflare Workers host the API without a separate Node server.
- Cloudflare D1 stores relational data.
- Cloudflare Pages can host the static React app.
- Cron Triggers run scheduled checks.
- Discord webhooks provide alerting without a paid notification provider.
Production usage should still consider Worker invocation limits, D1 read/write limits, Cron frequency, and check volume.
- Passwords are hashed with PBKDF2-SHA256.
- JWTs are signed with
JWT_SECRET; the cron fallback secret is compared in constant time. - Secrets are never hardcoded.
- Zod validates request bodies.
- Monitor URLs are restricted to
http/https, and alert webhooks are restricted to Discord webhook endpoints so the Worker never POSTs to arbitrary destinations. - D1-backed rate limits protect auth, monitor creation, manual checks, and cron fallback.
- CORS allows configured frontend origin or localhost development origins.
- Security headers include
X-Content-Type-Options,X-Frame-Options,Referrer-Policy, and restrictivePermissions-Policy. - Public status routes intentionally omit user IDs, webhook URLs, and private monitor fields.
Run the full local validation path:
corepack pnpm lint
corepack pnpm typecheck
corepack pnpm test
corepack pnpm buildBackend service tests use Vitest and mocked D1/fetch behavior. They do not require a Cloudflare account or a Discord webhook.
- Multi-region checks.
- Team accounts and role-based access.
- Email or Slack alerts.
- Monitor tags and filtering.
- Custom status page branding.
- Status page custom domains.
- More granular uptime windows.
- Frontend component tests.
- Cloudflare analytics integration.






