Job hunting is a numbers game, and keeping track of dozens of applications across LinkedIn, Indeed, company sites, and cold emails quickly becomes chaotic. I built AppTrack — a full-stack job application tracker with resume-JD matching, pipeline analytics, and smart follow-up reminders. Here's how.
The Problem
When you're actively job hunting, you need to track:
- Where you applied and when
- Current status of each application
- Which sources (LinkedIn, referral, etc.) actually get responses
- When to follow up
- How well your resume matches each role
Spreadsheets work initially, but they don't scale. You need filtering, analytics, and automation.
Architecture
┌─────────────────────────────────────┐
│ Frontend (SPA) │
│ Tailwind CSS + Alpine.js + Chart │
└──────────────┬──────────────────────┘
│ REST API
┌──────────────▼──────────────────────┐
│ FastAPI Backend │
│ ┌─────────┐ ┌─────────┐ ┌──────┐ │
│ │ CRUD │ │Analytics│ │Match │ │
│ │ Router │ │ Router │ │Router│ │
│ └────┬────┘ └────┬────┘ └──┬───┘ │
│ │ │ │ │
│ ┌────▼───────────▼─────────▼───┐ │
│ │ Service Layer │ │
│ │ ┌──────┐ ┌─────┐ ┌──────┐ │ │
│ │ │App │ │Stats│ │TF-IDF│ │ │
│ │ │Svc │ │ Svc │ │Match │ │ │
│ │ └──┬───┘ └──┬──┘ └──┬───┘ │ │
│ └─────┼────────┼───────┼──────┘ │
│ │ │ │ │
│ ┌─────▼────────▼───────▼──────┐ │
│ │ SQLite (aiosqlite) │ │
│ │ applications | events | │ │
│ │ contacts | reminders │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
Tech Stack
| Component | Technology | Why |
|---|---|---|
| API Framework | FastAPI | Auto-generated OpenAPI docs, async, type-safe |
| Database | SQLite + aiosqlite | Zero config, async, perfect for personal tools |
| Matching | scikit-learn TF-IDF | No external APIs needed, fast, interpretable |
| Frontend | Tailwind + Alpine.js | Lightweight, no build step needed |
| Charts | Chart.js | Beautiful charts with minimal code |
| CLI | Click + Rich | Terminal-first workflow |
| CI | GitHub Actions | Automated testing on push |
Key Feature: Resume-JD Matching
The most interesting feature is the TF-IDF-based resume matcher. It scores how well your resume matches a job description — completely offline, no API costs.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
def score_match(resume_text: str, job_description: str) -> dict:
vectorizer = TfidfVectorizer(
stop_words="english",
ngram_range=(1, 2),
max_features=5000,
sublinear_tf=True,
)
tfidf_matrix = vectorizer.fit_transform([resume_text, job_description])
similarity = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])
score = round(float(similarity[0][0]) * 100, 1)
# Extract matching and missing keywords
jd_keywords = extract_keywords(job_description)
resume_keywords = extract_keywords(resume_text)
matching = jd_keywords & resume_keywords
missing = jd_keywords - resume_keywords
return {
"score": score,
"matching_keywords": sorted(matching),
"missing_keywords": sorted(missing),
"suggestion": generate_suggestion(score, missing),
}
The key decisions:
-
ngram_range=(1, 2)captures both single words ("python") and two-word phrases ("data engineering") -
sublinear_tf=Trueapplies logarithmic TF scaling so common words don't dominate - Keyword extraction uses a curated tech vocabulary plus regex for acronyms/proper nouns
This gives you a practical score plus actionable feedback: which keywords you match and which are missing.
Smart Reminders
When you create an application, AppTrack automatically sets a 7-day follow-up reminder. When you move an application to an interview stage, it creates:
- An interview prep reminder (immediate)
- A thank-you note reminder (1 day after)
async def update_status(app_id: str, new_status: str, note: str = None):
# Update the status
await db.execute(
"UPDATE applications SET status = ?, updated_at = ? WHERE id = ?",
(new_status, now, app_id),
)
# Log the event
await db.execute(
"INSERT INTO events (...) VALUES (...)",
(event_id, app_id, 'status_change', old_status, new_status, now),
)
# Auto-create interview reminders
if new_status in {"phone_screen", "technical", "onsite"}:
await create_reminder(app_id, "interview_prep", "Prepare for interview")
await create_reminder(app_id, "thank_you", "Send thank-you note", days=1)
The Dashboard
The frontend is a single HTML file using CDN-loaded Tailwind CSS, Alpine.js, and Chart.js. Four tabs:
- Applications — Sortable, filterable table with inline status updates
- Analytics — Pipeline funnel, weekly trends, source breakdown charts
- Match Scorer — Paste a JD, get instant match analysis
- Reminders — Pending follow-ups with dismiss functionality
No build step needed. Just serve the HTML.
Pipeline Analytics
The analytics module queries SQLite to calculate:
- Response rate: % of applications that moved past "applied"
- Source effectiveness: Which sources (LinkedIn vs referral vs cold email) convert best
- Pipeline funnel: Visual breakdown of where applications are in the process
- Weekly trends: Application velocity over time
async def get_sources():
rows = await db.execute_fetchall("""
SELECT
COALESCE(source, 'unknown') as source,
COUNT(*) as cnt,
SUM(CASE WHEN status IN ('phone_screen', 'technical', 'onsite', 'offer', 'accepted')
THEN 1 ELSE 0 END) as interview_cnt
FROM applications
GROUP BY source
ORDER BY cnt DESC
""")
return [{
"source": r["source"],
"count": r["cnt"],
"conversion_rate": round(r["interview_cnt"] / r["cnt"] * 100, 1)
} for r in rows]
This is the data that actually helps you optimize your job search strategy.
Full REST API
The API covers everything:
POST /api/applications Create application
GET /api/applications List with filters/pagination
GET /api/applications/{id} Get details + timeline
PUT /api/applications/{id} Update fields
PATCH /api/applications/{id}/status Update status
DELETE /api/applications/{id} Delete
GET /api/analytics/overview Summary stats
GET /api/analytics/pipeline Funnel data
GET /api/analytics/trends Weekly trends
GET /api/analytics/sources Source effectiveness
POST /api/match/score Score resume vs JD
POST /api/import/csv Import from CSV
GET /api/export/csv Export to CSV
GET /api/reminders Pending reminders
PATCH /api/reminders/{id} Dismiss/snooze
FastAPI auto-generates interactive Swagger docs at /docs — great for recruiter demos.
Testing
34 tests covering CRUD, analytics, matching, reminders, and integration scenarios:
$ pytest tests/ -v
========================= test session starts =========================
tests/test_analytics.py::test_overview_empty PASSED
tests/test_analytics.py::test_overview_with_data PASSED
tests/test_analytics.py::test_pipeline PASSED
tests/test_api.py::test_full_application_lifecycle PASSED
tests/test_api.py::test_csv_export PASSED
tests/test_applications.py::test_create_application PASSED
tests/test_applications.py::test_status_change_creates_event PASSED
tests/test_matcher.py::test_score_match_basic PASSED
tests/test_matcher.py::test_score_match_keywords PASSED
tests/test_reminders.py::test_reminders_created_on_apply PASSED
... (34 total)
========================= 34 passed in 0.30s =========================
Tests use an in-memory SQLite database and async HTTP client — fast and isolated.
Running It
# Clone and install
git clone https://github.com/hajirufai/apptrack.git
cd apptrack
pip install -r requirements.txt
# Run
python -m uvicorn app.main:app --reload
# Or with Docker
docker compose up -d
Visit http://localhost:8000 for the dashboard, /docs for the API.
What I'd Add Next
- Email parsing: Auto-extract application data from confirmation emails
- Browser extension: Quick-add from job listing pages
- Salary tracking: Compare offers with market data
- AI cover letter drafts: Generate tailored cover letters from the match analysis
Key Takeaways
-
SQLite is underrated for personal tools — zero config, fast, and
aiosqlitemakes it async-compatible - TF-IDF matching gives surprisingly useful results for resume-JD comparison without any API costs
- Auto-generated reminders prevent the #1 job search mistake: forgetting to follow up
- CDN-loaded frontend (Tailwind + Alpine.js) means zero build complexity for dashboard UIs
- Build what you need — the best portfolio projects solve your own problems
Check out the full source on GitHub. If you're job hunting, feel free to fork it and track your own applications!
Top comments (0)