A small full-stack web application for tracking job applications through four hiring stages: applied, interview, rejected, and offer.
The app gives a job seeker one place to add, review, search, filter, edit, and delete applications. The dashboard counts are calculated from the same SQLite data used by the application list, so updates are immediately reflected in the UI.
React + Vite browser
│ Fetch API / JSON
▼
FastAPI REST API ── Pydantic validation
│
▼
SQLAlchemy ORM ── SQLite (backend/job_tracker.db)
The backend uses a simple module layout: routes live in app/main.py, persistence configuration in app/database.py, the table mapping in app/models.py, validation/response types in app/schemas.py, and database operations in app/crud.py. The frontend keeps the API client in src/api.js and uses focused React components for the dashboard, list, form, and details view.
- Python 3.12+
- FastAPI and Uvicorn
- SQLAlchemy 2.0
- SQLite
- Pydantic 2
- Pytest and FastAPI TestClient
- React 18
- Vite
- Plain CSS
- Browser Fetch API
From the project root:
cd backend
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txtIn a second terminal:
cd frontend
npm installbackend/job_tracker.db is created automatically the first time the API starts. It is ignored by Git so local application data stays local.
cd backend
source .venv/bin/activate
uvicorn app.main:app --reload --port 8000API base URL: http://127.0.0.1:8000
Swagger UI: http://127.0.0.1:8000/docs
cd frontend
npm run devOpen http://127.0.0.1:5173. The frontend defaults to http://127.0.0.1:8000 for the API. To use another API URL, create frontend/.env with:
VITE_API_URL=http://localhost:8000The backend accepts FRONTEND_URL as a comma-separated list if the frontend is hosted at a different origin.
cd backend
source .venv/bin/activate
python3 -m pytest -q
cd ../frontend
npm run build| Method | Endpoint | Purpose | Success |
|---|---|---|---|
POST |
/applications |
Create an application | 201 |
GET |
/applications |
List all applications | 200 |
GET |
/applications/{id} |
Read one application | 200 / 404 |
PATCH |
/applications/{id} |
Partially update an application | 200 / 404 |
DELETE |
/applications/{id} |
Delete an application | 204 / 404 |
GET |
/applications?status=interview |
Filter by status | 200 |
GET |
/applications?search=google |
Search company or role | 200 |
GET |
/stats |
Read dashboard counts | 200 |
GET |
/health |
Check API availability | 200 |
Allowed statuses are applied, interview, rejected, and offer. Required fields are company, role, status, and applied_date; salary and notes are optional.
The main dashboard includes statistic cards, a search field, a status filter, and the application list. The add/edit form and details view are available from the same interface. To capture local screenshots, run both services and use the browser's screenshot tool on http://127.0.0.1:5173.
- SQLite was chosen because the application needs persistence but no database server or Docker setup.
- SQLAlchemy keeps SQL concerns out of route functions and gives a clear ORM model for a fresher to explain.
- Pydantic create, update, and response schemas keep validation explicit and prevent the API from exposing ORM internals accidentally.
PATCHis used for editing because the form can send a partial update and the backend applies only supplied fields.- Search uses a case-insensitive match over both
companyandrole. - The frontend re-fetches applications and stats after every mutation. This keeps the browser state aligned with the real backend and makes persistence behavior obvious.
- Authentication, permissions, background jobs, and external integrations are intentionally out of scope.
- Add pagination when the application list becomes large.
- Add an optional follow-up date and reminders.
- Add database migrations with Alembic for schema evolution.
- Add frontend component tests and end-to-end tests in CI.
- Add an export-to-CSV action.
The local browser verification exercised:
- Start FastAPI and Vite.
- Create an application.
- Confirm it appears in the list and increments dashboard stats.
- Edit it from the details view.
- Filter it to
interview. - Search it by company.
- Confirm the updated status and stats are returned by the API and shown by React.
- Delete it with the confirmation dialog.
- Reload/restart and confirm SQLite persistence.