The Story Behind the Build
Picture this: It’s 3 AM. You’re refreshing the ASU class search page for the hundredth time. That coding class you need to graduate? Still full. The professor everyone raves about? Still listed as “Staff.”
Sound familiar? It happened to me during my sophomore year.
I needed CSE 310 with Professor Martinez. The class was full. I set phone alarms every hour. I checked between classes. I even woke up in the middle of the night to refresh the page. When a seat finally opened at 2:47 AM on a Tuesday, I was asleep. Someone else got it.
That’s when I remembered pickaclass.app—a tool that watched classes for you. It sent emails when seats opened up. Perfect solution, right? Except it shut down in 2023.
So I built my own.
What PickMyClass Does
PickMyClass is a notification system for university students. Enter your section number, and the app does the watching for you.
The system checks every 30 minutes for two things:
- Open seats – Get notified the moment a full class has availability
- Instructor updates – Know when “Staff” changes to an actual professor name
No more constant refreshing. No more missed opportunities. Just an email when something changes.
The Architecture
Here’s how the system flows:
User Request → Cloudflare Worker (Edge)
↓
Supabase (Auth + DB)
↓
Cloudflare Queue (Job)
↓
Oracle Cloud (Puppeteer Scraper)
↓
Supabase (Update DB)
↓
Resend API (Email Notification)
The architecture keeps costs low: Cloudflare handles edge logic for free, Oracle’s always-free tier runs the heavy Puppeteer work, and Supabase’s generous free tier stores user data.
The Technical Challenge
Building this wasn’t straightforward. ASU’s class search loads data with JavaScript. Their API returns 401 errors without browser context. Simple HTTP requests don’t work.
The solution? A Puppeteer-based scraper running on Oracle Cloud. It uses a real Chrome browser to fetch class data.
Here’s the core scraping logic:
// Wait for the class results to load
await page.waitForSelector('.class-results-row', { timeout: 30000 });
// Extract class data from the page
const classData = await page.evaluate(() => {
const rows = document.querySelectorAll('.class-results-row');
return Array.from(rows).map(row => ({
sectionNumber: row.querySelector('.section-id')?.textContent?.trim(),
seatsAvailable: parseInt(
row.querySelector('.seats-open')?.textContent || '0'
),
instructor: row.querySelector('.instructor-name')?.textContent?.trim()
|| 'Staff',
}));
});
The tricky part? Handling authentication cookies, session timeouts, and random 401 errors. Circuit breaker patterns prevent one failed request from crashing everything.
The 2,500x Query Optimization
Initially, each class check triggered individual database queries. With 10,000 users watching 3 classes each, that’s 30,000 queries per cron run. Unacceptable.
The fix: batch everything.
-- Before: Individual queries per class (30,000 queries)
SELECT * FROM watches WHERE section_id = $1;
-- After: Single batch query (1 query)
SELECT section_id, array_agg(user_id) as watchers
FROM watches
WHERE section_id = ANY($1)
GROUP BY section_id;
One query fetches all watches. One query updates all changes. The email API supports batch sends. Total queries dropped from 30,000 to under 12.
Cost Breakdown at Scale
Here’s what it costs to run PickMyClass for 10,000 users:
| Service | Monthly Cost | Notes |
|---|---|---|
| Oracle Cloud | $0 | Always-free tier for Puppeteer VM |
| Cloudflare Workers | ~$5 | Beyond free tier at scale |
| Supabase | $25 | Pro tier for reliability |
| Resend | ~$4 | Email volume pricing |
| Total | ~$34 | $0.0034 per user |
Compare that to a traditional AWS setup that would run $200-500/month for the same workload.
Working With (Not Against) ASU’s Systems
Web scraping has ethics. Here’s how PickMyClass stays responsible:
- 30-minute intervals – Not hammering their servers every minute
- Request queuing – Max 5 concurrent scraper instances
- Exponential backoff – Failed requests wait longer before retry
- User-Agent honesty – Not pretending to be Googlebot
- Circuit breakers – If ASU is having issues, we stop trying temporarily
The goal is to be a good citizen. If everyone scraped irresponsibly, they’d block everyone.
Built With Modern Web Tech
The stack includes:
- Next.js 15 for the frontend
- Cloudflare Workers for edge computing
- Supabase for authentication and real-time features
- PostgreSQL with row-level security
- Puppeteer for web scraping
- Resend for transactional emails
The dashboard updates live. Add a class watch, and it appears instantly. When the cron job detects changes, your browser sees them without refreshing.
Why It Matters
Getting into the right class can change your college experience. The right professor makes difficult subjects click. Required courses at convenient times let you work or do internships. Popular electives fill up in minutes.
Students shouldn’t lose opportunities because they were in class or asleep. Technology should work for us, not against us.
PickMyClass levels the playing field. Whether you’re a morning person or a night owl, you get the same chance at that open seat.
What I Learned
This project taught me how to:
- Build fault-tolerant systems at scale
- Work with headless browsers and anti-bot measures
- Design database schemas with security policies
- Implement edge caching for performance
- Handle webhook integrations for email delivery
- Set up self-hosted logging to debug scraper failures at 3 AM
But the biggest lesson? Solve problems you actually have. The best projects come from real frustrations. When you’ve experienced the problem firsthand, you know exactly what the solution needs to do.
Looking Forward
PickMyClass is live and watching classes right now. The system currently supports ASU, but the architecture works for any school with a web-based class search.
Need to track a class at your university? Visit pickmyclass.app to get started. The notification system is designed to be adaptable to different institutions with similar class registration challenges.