A high-performance, scalable class seat notification system for university students. Monitor class availability, get notified when seats open up, and track instructor assignments.
Built with vinext (Vite-based Next.js), Supabase, and deployed on Cloudflare Workers for edge performance.
- Seat Monitoring - Track when seats become available in full classes
- Instructor Tracking - Get notified when "Staff" instructors are assigned to specific professors
- Real-time Updates - Dashboard updates live via Supabase Realtime subscriptions
- Email Notifications - Instant email alerts via Resend when changes are detected
- Smart Deduplication - Prevents duplicate notifications using atomic PostgreSQL operations
- Scalable Queue Processing - Handles 10,000+ users with parallel Cloudflare Queues
- 30-Minute Checks - Automated checks via Cloudflare Workers Cron Triggers
We chose Cloudflare Workers as our deployment platform for several compelling reasons:
- Global Distribution: Code runs in 300+ data centers worldwide, ensuring low latency for all users
- No Cold Starts: Workers are always warm, providing consistent sub-100ms response times
- Smart Placement: Automatic routing to the nearest data center
- Generous Free Tier: 100,000 requests/day free, more than enough for most deployments
- Pay-Per-Use: Only pay for actual compute time, not idle servers
- No Infrastructure Management: Zero DevOps overhead
- Cloudflare Queues: Reliable message queue for processing class checks at scale
- Durable Objects: Distributed coordination for circuit breakers and cron locks
- Workers KV: Edge caching for fast data retrieval
- Hyperdrive: Connection pooling for PostgreSQL with automatic optimization
- Automatic Failover: Built-in redundancy across data centers
- DDoS Protection: Enterprise-grade security by default
- 99.99% Uptime SLA: Production-grade reliability
- vinext Compatibility: Deploy apps on Cloudflare Workers via Vite-based build
- Instant Deployments: Sub-second deployments via Wrangler CLI
- Integrated Monitoring: Real-time logs and analytics
User Browser
|
v
vinext App (Cloudflare Workers) <---> Supabase (Auth + PostgreSQL + Realtime)
|
v
Cloudflare Cron (every 30 min)
|
v
Cloudflare Queue (pickmyclass-queue)
|
v
Queue Consumers (100+ concurrent Workers)
|
v
ASU Class Search API (direct HTTP calls)
|
v
Change Detection --> Resend Email API --> User Notifications
| Component | Purpose |
|---|---|
worker.ts |
Custom Cloudflare Worker with cron, queue handlers, and Durable Objects |
app/api/cron/route.ts |
Cron job entry point - enqueues sections to queue |
app/api/queue/process-section/route.ts |
Queue consumer - processes single section |
lib/db/queries.ts |
Database query helpers with atomic deduplication |
lib/asu/api.ts |
ASU Class Search API client (direct HTTP) |
CronLockDO - Prevents duplicate cron executions
- Auto-expires after 25 minutes
- Ensures only one cron job runs at a time across all isolates
- Bun (package manager)
- Supabase Account (free tier available)
- Cloudflare Account (free tier available)
- Resend Account (free tier: 100 emails/day)
- ASU API access (configured via
ASU_API_BASE_URLandASU_API_TOKEN)
git clone https://github.com/yourusername/pickmyclass.git
cd pickmyclass
bun install- Create a new project at supabase.com
- Link your local project:
bunx supabase link --project-ref your-project-id
- Push database migrations:
bunx supabase db push
- Generate TypeScript types:
bunx supabase gen types typescript --linked > lib/supabase/database.types.ts
Copy .env.example to .env.local:
cp .env.example .env.localRequired variables:
| Variable | Description | Where to Get It |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Your Supabase project URL | Supabase Dashboard -> Settings -> API |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anonymous key | Supabase Dashboard -> Settings -> API |
SUPABASE_SERVICE_ROLE_KEY |
Service role key (bypasses RLS) | Supabase Dashboard -> Settings -> API |
ASU_API_BASE_URL |
Base URL for ASU Class Search API | ASU API endpoint |
ASU_API_TOKEN |
Auth token for ASU API | Generate: openssl rand -hex 32 |
RESEND_API_KEY |
Resend API key | resend.com/api-keys |
CRON_SECRET |
Auth for cron endpoint | Generate: openssl rand -hex 32 |
Edit wrangler.jsonc and update the placeholder values:
Note:
ASU_API_BASE_URLandASU_API_TOKENare configured as Cloudflare encrypted secrets (not vars) to avoid exposing the API endpoint in source code. Set them viawrangler secret put(see step 5 below).
Optionally configure a custom domain:
```jsonc
{
"routes": [
{
"pattern": "your-domain.com",
"custom_domain": true
}
]
}
# Authenticate with Cloudflare
wrangler login
# Set secrets (you'll be prompted for values)
wrangler secret put NEXT_PUBLIC_SUPABASE_ANON_KEY
wrangler secret put SUPABASE_SERVICE_ROLE_KEY
wrangler secret put ASU_API_BASE_URL
wrangler secret put ASU_API_TOKEN
wrangler secret put RESEND_API_KEY
wrangler secret put RESEND_WEBHOOK_SECRET
wrangler secret put CRON_SECRETbun run deployYour app will be live at https://your-worker.workers.dev or your custom domain.
Create the required queues in Cloudflare Dashboard:
- Go to Workers & Pages -> Queues
- Create
pickmyclass-queue - Create
pickmyclass-dlq(dead letter queue)
The app/legal/ directory contains Terms of Service and Privacy Policy pages with ASU-specific content and hardcoded email addresses ([email protected]). For your deployment:
- Update contact email addresses in:
app/legal/page.tsxapp/legal/terms/page.tsxapp/legal/privacy/page.tsx
- Review and customize legal content for your institution/jurisdiction
- Update the privacy policy to reflect your data practices
- Check the health endpoint:
https://your-domain.com/api/monitoring/health - Verify cron triggers in Cloudflare Dashboard -> Workers -> Triggers
- Test by adding a class watch in the dashboard
bun run dev # Start dev server (localhost:3000)bun run preview # Build with vinext and preview locallybun run build # Build application
bun run lint # Run Biome linter
bun run lint:fix # Fix lint issues
bun run format # Format code with Biome
bun run knip # Find unused exports/dependencies
bun run cf-typegen # Generate TypeScript types for Cloudflare envbunx supabase db push # Push migrations to remote
bunx supabase db pull # Pull remote schema changes
bunx supabase migration new <name> # Create new migration- Frontend: vinext (App Router), React 19, TypeScript, Tailwind CSS 4
- Backend: Cloudflare Workers (via vinext), Supabase (PostgreSQL + Auth + Realtime)
- Data Source: ASU Class Search API (direct HTTP)
- Email: Resend (transactional emails)
- Deployment: Cloudflare Workers + Pages
app/ # App Router
├── api/
│ ├── class-watches/ # CRUD API for user watches
│ ├── cron/ # Cloudflare Workers cron handler
│ ├── queue/ # Queue consumer handlers
│ └── webhooks/ # Resend webhook handlers
├── dashboard/ # Main dashboard with Realtime updates
├── login/ # Authentication pages
└── layout.tsx # Root layout
lib/
├── supabase/ # Supabase clients (browser, server, service)
├── db/ # Database query helpers
├── email/ # Resend integration + templates
└── hooks/ # React hooks (Realtime subscriptions)
components/
├── ui/ # shadcn/ui components
└── ... # Feature components
supabase/
└── migrations/ # Database migrations
worker.ts # Custom Cloudflare Worker
wrangler.jsonc # Cloudflare Workers config
- User adds class watch - Student enters section number on dashboard
- Every 30 minutes - Cloudflare cron triggers enqueue all watched sections
- Queue consumers process - 100+ Workers query ASU API in parallel
- Change detection - Compare new state with PostgreSQL cached state
- Atomic deduplication - PostgreSQL
INSERT...ON CONFLICTprevents race conditions - Email notification - Resend batch API sends alerts for available seats
- Real-time update - Dashboard reflects changes via Supabase Realtime
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Run linting:
bun run lint:fix - Commit with conventional commits:
git commit -m "feat: add new feature" - Push and open a PR
This project is licensed under the MIT License - see the LICENSE file for details.
{ "vars": { "NOTIFICATION_FROM_EMAIL": "[email protected]", "NEXT_PUBLIC_SITE_URL": "https://your-domain.com", "NEXT_PUBLIC_SUPABASE_URL": "https://your-project-id.supabase.co" } }