A self-hosted website uptime monitoring platform. Add URLs to watch, get alerted when they go down, track response times, manage incidents, and share public status pages — all in one monorepo.
flowchart LR
subgraph Clients
Browser["Browser"]
end
subgraph Apps
Web["Web\nNext.js 15\n:3000"]
API["API\nExpress + Zod\n:3001"]
Pusher["Pusher\nScheduler"]
Worker["Worker\nMonitor"]
end
subgraph Data
PG[("PostgreSQL")]
Redis[("Redis Stream")]
end
subgraph External
Sites["Monitored URLs"]
Resend["Resend\nEmail alerts"]
Webhooks["Webhooks\nSlack etc."]
end
Browser --> Web
Web -->|REST| API
API --> PG
Pusher -->|"read due sites"| PG
Pusher -->|"enqueue"| Redis
Redis -->|"consume"| Worker
Worker -->|"write ticks / incidents"| PG
Worker -->|HTTP ping| Sites
Worker --> Resend
Worker --> Webhooks
- Pusher runs on a 30-second tick and enqueues every website that is due for a check into a Redis stream.
- Worker reads from the stream, HTTP-pings each URL, records the result (status + response time) to Postgres, opens/closes incidents on state transitions, and fires email/webhook alerts.
- API exposes a REST interface for the web frontend — auth, website CRUD, history, alert settings, incidents, maintenance windows, and public status pages.
- Web is the Next.js dashboard where users sign up, add sites, view uptime charts, and configure everything.
pinggod/
├── apps/
│ ├── api/ # Express REST API (auth, websites, incidents, alerts)
│ ├── pusher/ # Scheduler — enqueues websites into Redis stream
│ ├── worker/ # Monitor — pings URLs, records ticks, fires alerts
│ ├── tests/ # Integration test suite
│ └── web/ # Next.js dashboard (frontend)
└── packages/
├── store/ # Prisma client + schema (single source of database truth)
├── redisstream/ # Redis stream helpers (xAdd, xReadGroup, xAck)
├── ui/ # Shared React component library
├── eslint-config/ # Shared ESLint rules
└── typescript-config/ # Shared tsconfig bases
| Layer | Technology |
|---|---|
| Runtime | Bun |
| Monorepo | Turborepo |
| Frontend | Next.js 15, Tailwind CSS |
| API | Express, Zod, JWT + refresh tokens |
| Database | PostgreSQL via Prisma |
| Queue | Redis streams |
| Email alerts | Resend |
| Webhook alerts | Generic HTTP + Slack-compatible |
- Bun >= 1.2.2
- Node >= 18
- PostgreSQL running locally (or a connection string)
- Redis running locally
bun installEach app needs a .env file. Start from the examples:
cp packages/store/.env.example packages/store/.envEdit packages/store/.env:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/pinggodCreate apps/api/.env:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/pinggod
JWT_SECRET=your-super-secret-key
CORS_ORIGIN=http://localhost:3000
PORT=3001Create apps/worker/.env:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/pinggod
REDIS_URL=redis://localhost:6379
REGION_ID=us-east-1
WORKER_ID=worker-1
RESEND_API_KEY=re_... # optional — needed for email alertsCreate apps/pusher/.env:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/pinggod
REDIS_URL=redis://localhost:6379Create apps/web/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:3001cd packages/store
bunx prisma migrate deploybun run devTurborepo starts all apps in parallel:
| Service | Default port |
|---|---|
| Web dashboard | http://localhost:3000 |
| API | http://localhost:3001 |
| Pusher | — (no HTTP port) |
| Worker | — (no HTTP port) |
Run from the repo root:
bun run dev # start all apps in watch mode
bun run build # production build for all apps
bun run lint # lint all packages
bun run check-types # typecheck all packages
bun run format # prettier across the entire repo- Uptime monitoring — configurable check intervals (30s, 1m, 2m, 5m, 10m)
- Response time tracking — stored per-tick with regional tagging
- Incident management — auto-opened on first failure, auto-resolved on recovery
- Keyword monitoring — assert a string is present/absent in the response body
- SSL certificate monitoring — tracks expiry, alerts when < 14 days remain
- Alert channels — email (via Resend) and arbitrary webhooks (Slack-compatible)
- Maintenance windows — suppress alerts during planned downtime
- Public status pages — shareable
/status/:slugpages with no login required - Bulk import — import monitors from a CSV or migrate from UptimeRobot
- JWT auth — 15-minute access tokens with 30-day rotating refresh tokens
cd apps/tests
bun run testThe integration tests expect a running Postgres and API server. Set API_URL and DATABASE_URL in apps/tests/.env before running.