Lightweight website uptime monitoring service built with Go (Gin + GORM) and a Next.js frontend.
- Authenticated REST API (JWT) with middleware-based protection
 - Create, list, update, and delete monitors per user
 - Background worker periodically checks endpoints and stores logs
 - Email alerts via SendGrid on status change (DOWN and back UP)
 - Frontend dashboard with mini uptime charts and monitor management
 
- 
Environment
JWT_SECRET=your_secret- Database (configure in 
config/db.go); run migrations viamigrations/migrations.go - Optional for email alerts: 
SENDGRID_API_KEY(configured inconfig/sendgrid.go) 
 - 
Run
 
go mod download
go run ./main.goThe API defaults to http://localhost:8080 with routes under /api (see routes/).
middlewares/checkAuth.go: validates JWT and injectsuser_idinto the Gin contextmodels/: GORM models and DB helpersservice/: application logic (monitor worker, monitor CRUD, uptime logs)controller/: HTTP handlers, request binding, and responsesroutes/: route groups; monitors are behind auth middleware
We use a singleton to coordinate all background checks across the app. This ensures only one in-memory worker owns and schedules monitor jobs, even if multiple code paths try to start it.
Implementation: service/monitor_worker.go
GetMonitorWorker()usessync.Onceto create exactly oneMonitorWorkerinstance and returns it thereafter.- The worker maintains an in-memory registry 
map[uint]*MonitorJobfor active monitors, each with its own ticker and stop channel. - On startup (
Start), the worker loads existing monitors from the database and adds jobs with parsed intervals. - The main loop (
Run) periodically triggers checks, andrunMonitorJobruns a persistent goroutine per monitor for continuous checks. - On status change (UP ↔ DOWN), 
sendMonitorAlertsends emails when SendGrid is configured. 
Why singleton?
- Prevents duplicate scheduling and race conditions
 - Centralizes lifecycle management (add/update/delete jobs)
 - Simple, process-local coordination without external brokers
 
Relevant snippets:
// service/monitor_worker.go
var (
    worker *MonitorWorker
    once   sync.Once
)
func GetMonitorWorker() *MonitorWorker {
    once.Do(func() {
        ctx, cancel := context.WithCancel(context.Background())
        worker = &MonitorWorker{ Monitors: make(map[uint]*MonitorJob), Ctx: ctx, Cancel: cancel }
    })
    return worker
}Worker-aware updates:
- When a monitor is created, 
service.CreateMonitorparses its interval andAddMonitorregisters a job. - When a monitor is deleted, 
service.DeleteMonitorcallsDeleteMonitorto stop and remove the job. - When a monitor is updated, 
service.UpdateMonitorpersists changes and, if the interval changed, removes and re-adds the job with the new schedule. 
POST /api/monitors/createGET /api/monitors/user/:userIdlist for current userGET /api/monitors/:idgetPUT /api/monitors/:idupdateDELETE /api/monitors/:iddeleteGET /api/monitors/:id/uptimelast 24h uptime samples
All monitor routes require Authorization: Bearer <token>.
The Next.js app (in GopherPingFrontend/) reads NEXT_PUBLIC_API_BASE_URL and uses the JWT from localStorage. The dashboard includes:
- Monitor list with mini uptime charts (last 24 samples)
 - Edit and destructive delete (with URL confirmation)
 
MIT