A high-performance, scalable uptime monitoring system built with TypeScript, featuring a microservices architecture powered by Redis Streams and PostgreSQL.
- Real-time Uptime Monitoring: Continuous website availability checks
- Multi-region Support: Monitor from USA and India regions
- Response Time Tracking: Detailed performance metrics
- Historical Data: Store and analyze uptime history
- Bulk Operations: Efficient processing of multiple monitors
- JWT-based Authentication: Secure user sessions
- User Registration & Login: Complete auth flow
- Protected Routes: Middleware-based authorization
- Password Hashing: Secure credential storage
- RESTful API: Clean, well-structured endpoints
- CRUD Operations: Full monitor lifecycle management
- Result Queries: Fetch uptime data with filtering
- Data Cleanup: Automatic old data removal
- Error Handling: Comprehensive error management
- Microservices Design: Separated concerns for better maintainability
- Redis Streams: Reliable message queuing and processing
- Producer-Worker Pattern: Scalable distributed processing
- Auto-recovery: PEL (Pending Entry List) message reclaiming
- Docker Ready: Containerized deployment
graph TD
User([User]) -->|HTTP Request| API[API Service]
API -->|Save Monitor| DB[(PostgreSQL)]
API -->|Push to Stream| Redis[(Redis Streams)]
Producer[Producer Service] -.->|Poll Monitors| DB
Producer -.->|Batch Push| Redis
subgraph Workers [Worker Cluster]
Worker1[Worker 1]
Worker2[Worker 2]
WorkerN[Worker N]
end
Redis -->|Consume| Workers
Workers -->|Perform Checks| Web([Internet])
Workers -->|Store Results| DB
Workers -->|Acknowledge| Redis
Reclaimer[PEL Reclaimer] -.->|Monitor Stalled| Redis
Reclaimer -.->|Reclaim & Process| Redis
better-uptime/
βββ apps/
β βββ api/ # REST API service
β β βββ src/
β β β βββ controllers/ # Request handlers
β β β βββ middleware/ # Auth middleware
β β β βββ models/ # Data models
β β β βββ routes/ # API routes
β β β βββ types/ # TypeScript types
β β β βββ utils/ # JWT utilities
β β βββ package.json
β βββ producer/ # URL batch producer
β β βββ index.ts
β βββ worker/ # Uptime check worker
β βββ index.ts
βββ packages/
β βββ db/ # Database package
β β βββ prisma/ # Database schema
β β βββ generated/ # Prisma client
β βββ redis-stream/ # Redis Stream utilities
β βββ utils/ # Shared utilities
βββ docker/ # Docker configuration
β βββ docker-compose.yml
β βββ postgres_data/ # PostgreSQL data
β βββ redis_data/ # Redis data
βββ README.md
- Runtime: Bun (TypeScript runtime)
- API Framework: Express.js
- Database: PostgreSQL with Prisma ORM
- Message Queue: Redis Streams
- Authentication: JWT tokens
- Containerization: Docker & Docker Compose
- Language: TypeScript
- Bun installed
- Docker installed
- Docker Compose installed
-
Clone the repository
git clone <repository-url> cd better-uptime
-
Install dependencies
bun install
-
Start the infrastructure
cd docker docker-compose up -d -
Set up the database
cd packages/db bun run prisma migrate dev bun run prisma generate -
Start the services
# Terminal 1 - API Service bun run api # Terminal 2 - Producer Service bun run producer # Terminal 3 - Worker Service bun run worker
POST /api/auth/signup- Register new userPOST /api/auth/login- Login userPOST /api/auth/logout- Logout userGET /api/auth/me- Get user profile
POST /api/monitor- Create new monitorGET /api/monitor- List user monitorsGET /api/monitor/:id- Get monitor detailsPUT /api/monitor/:id- Update monitorDELETE /api/monitor/:id- Delete monitor
GET /api/results/:monitorId/checks- Get monitor resultsGET /api/results/:monitorId/latest- Get latest resultDELETE /api/results/:monitorId/cleanup?days=90- Clean old results
Create a .env file in the root directory:
# Database
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/uptime"
# Redis
REDIS_URL="redis://localhost:6379"
STREAM_KEY="BetterUptime:Websites"
# JWT
JWT_SECRET="your-secret-key"
JWT_EXPIRES_IN="7d"
# API
PORT=3000
NODE_ENV="development"# API Service (Port 3000)
bun run api
# Producer Service (Runs every 3 minutes)
bun run producer
# Worker Service (Continuous processing)
bun run worker# Build and run with PM2 or similar process manager
bun run build
pm2 start ecosystem.config.jsid(UUID, Primary Key)email(String, Unique)name(String)hashedPassword(String)createdAt(DateTime)updatedAt(DateTime)
id(UUID, Primary Key)url(String)userId(UUID, Foreign Key)createdAt(DateTime)updatedAt(DateTime)
id(UUID, Primary Key)monitorId(UUID, Foreign Key)status(Enum: Up/Down)Location(Enum: USA/India)responseTimeMs(Integer)checkedAt(DateTime)
- User Action: When a user adds a new website URL through the Frontend/API, the
API Servicevalidates the input and creates a new entry in thePostgreSQLdatabase. - Task Distribution:
- Direct Push: The
API Serviceimmediately pushes the new monitor task to theRedis Stream(using theBetterUptime:Websiteskey) to ensure the first check happens instantly. - Periodic Sync: The
Producer Serviceruns every 3 minutes, fetching all active monitors from the database and pushing them in batches to theRedis Stream. This acts as a fallback and ensures continuous monitoring.
- Direct Push: The
- Check Execution:
Worker Servicesare organized intoConsumer Groups(e.g., by region like "india").- Workers use
XREADGROUPto pull unassigned tasks from the stream. - For each task, the worker performs an HTTP GET request to the target URL and measures the response time.
- Result Persistence:
- Workers collect results and perform a Bulk Upload to the
CheckResultstable inPostgreSQLusing Prisma'screateMany. - This minimizes database load and improves scalability.
- Workers collect results and perform a Bulk Upload to the
- Reliability & Self-Healing:
- Acknowledgement: After a successful upload, workers send an
XACKto Redis to mark the message as processed. - PEL Reclaiming: A
Reclaim Loopperiodically checks the Pending Entry List (PEL). If a message has been stuck (delivered but not acknowledged) for more than 30 seconds (e.g., due to a worker crash), the reclaimerXCLAIMs it and re-processes it.
- Acknowledgement: After a successful upload, workers send an
