PickMyClass: Never Miss Your Dream Class Again
TL;DR
PickMyClass is a notification system that monitors ASU class availability every 30 minutes and emails students when seats open or instructors are assigned, serving 10,000+ users for $34/month.
Key Takeaways
- Built to solve personal frustration with missing class seats at 2 AM
- Uses Puppeteer on Oracle Cloud to scrape JavaScript-rendered pages
- Scales to 10,000 users at $0.0034 per student per month
- Real-time updates via Supabase subscriptions
- 2,500x database query reduction through batch optimizations
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. PickMyClass checks every 30 minutes — not every minute — and caps itself at 5 concurrent scraper instances. Failed requests use exponential backoff rather than hammering retry loops. The scraper identifies itself honestly instead of spoofing Googlebot. Circuit breakers kick in if ASU’s systems are struggling, so the scraper backs off instead of piling on.
The goal is to be a good citizen. If everyone scraped irresponsibly, they’d block everyone.
The Stack
Next.js 15 for the frontend, Cloudflare Workers for edge logic, Supabase for auth and real-time subscriptions, PostgreSQL with row-level security, Puppeteer for scraping, and Resend for 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 the right class matters more than it probably should. The professor can make or break a subject. Timing affects whether you can hold a job or do an internship. And popular electives fill up in minutes — sometimes seconds.
I missed a seat because I was asleep at 2:47 AM. That’s a dumb reason to miss a class. PickMyClass fixes that specific problem: you watch your class, it watches the enrollment page, and you get an email the moment something opens. That’s all it does.
What I Learned
The most useful thing was the 2,500x query optimization — not because the number is impressive, but because the fix was embarrassingly simple. I was just missing indexes. No architectural overhaul, no new infrastructure. Just a SQL query that fetched all watches at once instead of one at a time. I’d been taught “add indexes early” and somehow still forgot until real users made the slowness obvious.
The scraper work taught me something less obvious: Puppeteer is overkill most of the time, until it’s the only thing that works. ASU’s class search requires browser context to return real data. There’s no way around it. Sometimes the annoying solution is just the right one.
I also learned to instrument everything early. Half my debugging on this project happened at 3 AM when the scraper silently failed during registration week. Without logs, I would have had no idea why.
Where It Stands
PickMyClass is running. 10,000+ users, $34/month, no serious outages.
Extending it to other universities is theoretically straightforward — each school needs its own scraper config — but I haven’t built that yet. ASU keeps me busy enough. If you’re at another school and want to adapt it, the code is open to fork.
The thing I built to solve my own problem turns out to be the thing other people actually use. That’s still a little surprising.
Frequently Asked Questions
How often does PickMyClass check for open seats?
Every 30 minutes. This balances timely notifications with responsible server load on ASU's systems.
Why use Puppeteer instead of direct API calls?
ASU's class search loads data with JavaScript and returns 401 errors without proper browser context. Puppeteer simulates a real Chrome browser to fetch the data successfully.
How much does it cost to run PickMyClass at scale?
About $34/month for 10,000 users. That breaks down to Oracle Cloud free tier for Puppeteer, Cloudflare Workers, Supabase, and Resend for emails.
Can PickMyClass work for other universities?
The architecture is designed to be adaptable. Any school with a web-based class search can be supported with a new scraper configuration.
How do you handle rate limiting and anti-bot detection?
Circuit breaker patterns prevent cascading failures. The scraper uses realistic browser fingerprints and respects rate limits to avoid being blocked.
Divanshu Chauhan (@divkix)
Software Engineer based in Tempe, Arizona, USA. More about divkix