A job tracking mobile app that monitors job boards for new internships and sends push notifications to your Android phone when new postings are found.
- 📱 Mobile App - React Native (Expo) app with clean Twitter/X-style job feed
- 🔔 Push Notifications - Instant alerts when new jobs are posted
- 🕷️ Web Scraping - Automatic scraping of We Work Remotely job board
- ☁️ Cloud Hosted - Backend deployed on Render (free tier)
- 💾 Supabase DB - PostgreSQL database for job and token storage
| Component | Technology |
|---|---|
| Frontend | React Native (Expo Router + Expo Notifications) |
| Backend | Python FastAPI |
| Database | Supabase (PostgreSQL) |
| Scraping | BeautifulSoup4 + Requests |
| Hosting | Render (free tier) |
| Automation | cron-job.org (free tier) |
firstapply/
├── mobile/ # React Native Expo app
│ ├── app/
│ │ ├── _layout.js # Navigation + notification setup
│ │ └── index.js # Job feed screen
│ ├── assets/ # App icons and images
│ ├── package.json
│ └── app.json
├── server/ # Python FastAPI backend
│ ├── main.py # API endpoints
│ ├── database.py # Supabase operations
│ ├── scraper.py # Web scraper + notifications
│ ├── requirements.txt
│ └── render.yaml # Render deployment config
└── README.md
- Python 3.9+
- Node.js 18+
- Android Studio (for emulator) or physical Android device
- Supabase account (free)
- Render account (free)
-
Create Project
- Go to supabase.com and sign up/login
- Click "New Project"
- Fill in project details and wait for setup (~2 minutes)
-
Create Database Tables
Go to SQL Editor (left sidebar) and run these commands:
-- Jobs table: stores scraped job listings CREATE TABLE jobs ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, company TEXT NOT NULL, url TEXT UNIQUE NOT NULL, posted_date TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); -- Add indexes for better query performance CREATE INDEX idx_jobs_url ON jobs(url); CREATE INDEX idx_jobs_created_at ON jobs(created_at DESC); -- Users table: stores Expo Push Tokens CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, expo_push_token TEXT UNIQUE NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() );
-
Get API Credentials
- Go to Settings → API (in left sidebar)
- Copy Project URL (e.g.,
https://xxxx.supabase.co) - Copy anon public key (under "Project API keys")
# Navigate to server directory
cd server
# Create virtual environment
python -m venv venv
# Activate virtual environment
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Create .env file with your Supabase credentials
# Copy the template or create manually:Create server/.env:
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_KEY=your-anon-public-key# Start the server
uvicorn main:app --reload --port 8000The API will be available at http://localhost:8000
Test the API:
# Health check
curl http://localhost:8000/
# Get jobs (will be empty initially)
curl http://localhost:8000/jobs
# Trigger scraper
curl http://localhost:8000/trigger-scrape# Navigate to mobile directory
cd mobile
# Install dependencies
npm install
# Create .env file for API URLCreate mobile/.env:
# For Android Emulator:
EXPO_PUBLIC_API_URL=http://10.0.2.2:8000
# For physical device (replace with your computer's IP):
# EXPO_PUBLIC_API_URL=http://192.168.1.100:8000
# For production:
# EXPO_PUBLIC_API_URL=https://firstapply-api.onrender.comFinding your local IP (for physical device):
- Windows: Run
ipconfig, look for IPv4 Address - macOS/Linux: Run
ifconfigorip addr
# Start Expo development server
npx expo start
# Press 'a' to open on Android emulator
# Or scan QR code with Expo Go app on physical deviceCreate a 96x96 pixel white icon on transparent background:
File: mobile/assets/notification-icon.png
Requirements:
- Size: 96x96 pixels
- White foreground (#FFFFFF)
- Transparent background
- PNG format
Quick option: Use any free icon generator or create a simple shape in any image editor.
Since Render's free tier doesn't include cron jobs, we use cron-job.org for free scheduling.
Render's free tier has two challenges:
- No cron jobs - Cron scheduling costs $$ on Render
- Server sleeps - Free tier spins down after 15 min of inactivity
Solution: External cron pinging our /trigger-scrape endpoint:
- ✅ Scrapes for new jobs
- ✅ Keeps server awake
- ✅ Completely free
-
Sign up at cron-job.org (free tier: 5 jobs)
-
Create a new cron job:
- URL:
https://your-app.onrender.com/trigger-scrape - Schedule: Every 15 minutes
- Method: GET
- URL:
-
Recommended schedule:
*/15 * * * *(every 15 minutes)- Balances freshness vs rate limits
- Keeps server from sleeping
-
Push code to GitHub
-
Connect to Render:
- Go to render.com and sign up
- Click "New +" → "Web Service"
- Connect your GitHub repo
- Render will auto-detect
render.yaml
-
Set Environment Variables:
- In Render dashboard → Environment
- Add
SUPABASE_URLandSUPABASE_KEY
-
Deploy!
- Render will build and deploy automatically
- Note your URL:
https://your-app.onrender.com
-
Update
mobile/.env:EXPO_PUBLIC_API_URL=https://your-app.onrender.com
-
Build APK:
# Install EAS CLI npm install -g eas-cli # Login to Expo eas login # Build APK eas build -p android --profile preview
# Start server
cd server
uvicorn main:app --reload --port 8000
# Test health
curl http://localhost:8000/
# Test scraping
curl http://localhost:8000/trigger-scrape
# Check jobs
curl http://localhost:8000/jobs# Start app
cd mobile
npx expo start
# Open on Android emulator (press 'a')After the app registers its token:
# Send test notification to all devices
curl -X POST http://localhost:8000/test-notification
# Or test with specific token
curl -X POST http://localhost:8000/test-notification \
-H "Content-Type: application/json" \
-d '{"token": "ExponentPushToken[xxx]"}'- ✅ App loads and shows job list
- ✅ Pull-to-refresh works
- ✅ Tapping job opens URL in browser
- ✅ Test notification appears on device
- ✅ Tapping notification opens job URL
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Health check |
/jobs |
GET | Get all jobs (newest first) |
/register-token |
POST | Register Expo Push Token |
/trigger-scrape |
GET | Run scraper manually |
/test-notification |
POST | Send test notification |
# Register token
curl -X POST http://localhost:8000/register-token \
-H "Content-Type: application/json" \
-d '{"token": "ExponentPushToken[abc123]"}'
# Get jobs
curl http://localhost:8000/jobs
# Trigger scrape
curl http://localhost:8000/trigger-scrape- Make sure API URL is
http://10.0.2.2:8000(notlocalhost) - Check that backend server is running
- Verify no firewall blocking port 8000
- Must test on physical device (emulators have limitations)
- Check notification permissions in device settings
- Verify token is registered: check Supabase
userstable - Test with
/test-notificationendpoint
- Site structure may have changed
- Check for rate limiting (429 errors)
- Verify internet connection
- Check server logs for errors
- Set up cron-job.org to ping every 15 minutes
- First request after sleep takes ~30 seconds (cold start)
The Android emulator uses special IP addresses:
10.0.2.2→ Host machine'slocalhost10.0.2.3→ Host machine's loopback interface
That's why we use http://10.0.2.2:8000 instead of http://localhost:8000
Tokens look like: ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]
These are unique per device and can expire when:
- App is uninstalled
- User revokes notification permissions
- Token rotation (rare)
Invalid tokens are automatically cleaned up when Expo returns DeviceNotRegistered
MIT License - feel free to use and modify!
- We Work Remotely for the job listings
- Expo for the amazing React Native tooling
- Supabase for the free PostgreSQL database
- Render for free hosting
- cron-job.org for free cron scheduling