A local web app for tracking job applications, resumes, and their statuses.
- Backend: Python 3 / FastAPI — REST API, file-based JSON storage
- Frontend: React + TypeScript / Chakra UI v3 / TanStack Query
You need two terminals.
Terminal 1 — backend
cd backend
uv run python -m uvicorn app.main:app --reloadTerminal 2 — frontend
cd frontend
npm install # first time only
npm run devOpen http://localhost:5173. On first launch you'll be prompted to create a username and password — no manual configuration needed.
Prefer a single-command setup? Docker Compose runs both services with no separate terminals needed.
Start
docker compose up --build -dOpen http://localhost. On first visit the browser setup page runs as usual — credentials are saved to ~/job_data/auth.json (the host-mounted data volume) and survive container restarts.
Stop
docker compose downData is stored in ~/job_data on your host machine — the same location used by the local setup, so both approaches share data seamlessly.
Edit .env at the project root to override defaults:
DATA_DIR=/home/you/job_data # where all data is stored (created automatically)
FRONTEND_ORIGIN=http://localhost:5173A .env.example file at the project root lists every available variable with descriptions.
On first run the app presents a setup page in the browser. Enter a username and password — credentials are hashed with bcrypt and stored in DATA_DIR/auth.json. No terminal access required.
Changing your password
Log in, go to Settings, and use the Change Password section. It generates a new bcrypt hash; copy it to DATA_DIR/auth.json (or AUTH_PASSWORD_HASH in .env) and restart the backend.
Token expiry
Login sessions expire after 24 hours. To change the window, add AUTH_TOKEN_EXPIRE_HOURS=<hours> to .env (e.g. 168 for 7 days).
Manual credential setup (advanced)
If you prefer to set credentials via environment variables instead of the browser setup page — for example in an automated deployment — add these to .env:
AUTH_USERNAME=admin
AUTH_PASSWORD_HASH=<bcrypt hash>
AUTH_SECRET_KEY=<64-char hex string>Generate a hash:
cd backend && uv run python -c "import bcrypt; print(bcrypt.hashpw(b'your-password', bcrypt.gensalt()).decode())"Generate a secret key:
python -c "import secrets; print(secrets.token_hex(32))"Environment variables take priority over auth.json, so the browser setup page is automatically disabled when they are set.
The nginx container listens on port 80 by default. To change it:
- Edit
frontend/nginx.conf— update thelistendirective:listen 3000;
- Edit
docker-compose.yml— updateFRONTEND_ORIGINon the backend service:- FRONTEND_ORIGIN=http://localhost:3000 - Rebuild:
docker compose up --build -d
- Track jobs with URL, company, position, description, notes, and source (LinkedIn, Indeed, etc.)
- Filter by status or source, or search by company / title / notes
- Associate a resume with each job at creation or later
- Progress through statuses as your application moves forward
Status flow:
Saved → Applied → Screening → Interviewing → Offer → Accepted
↘ Rejected
↘ Withdrawn
Any status can transition directly to Rejected or Withdrawn. Terminal statuses (Accepted, Rejected, Withdrawn) cannot be changed. The date is recorded automatically when a job moves to Applied.
- Create a resume from a blank template (.docx with placeholder sections)
- Copy an existing resume's .docx as a starting point
- Upload a .docx after editing in Word or LibreOffice
- Download any resume at any time
- Edit name, target role, and notes
$DATA_DIR/
├── auth.json # login credentials (written by setup flow)
├── jobs/
│ └── index.json # job records
└── resumes/
├── index.json # resume metadata
└── files/
└── {id}.docx
Back up $DATA_DIR to keep your records safe.
Interactive docs at http://localhost:8000/docs while the backend is running.
Auth
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/v1/auth/status |
— | Returns {"needs_setup": bool} |
| POST | /api/v1/auth/setup |
— | First-run credential creation (disabled once configured) |
| POST | /api/v1/auth/token |
— | Login — returns a Bearer token |
| POST | /api/v1/auth/generate-credentials |
Required | Generate a new password hash or secret key |
Jobs
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/jobs |
List jobs (?status= ?source=) |
| POST | /api/v1/jobs |
Create job |
| PATCH | /api/v1/jobs/{id} |
Update job / advance status |
| DELETE | /api/v1/jobs/{id} |
Delete job |
Resumes
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/resumes |
List resumes |
| POST | /api/v1/resumes |
Create resume |
| PATCH | /api/v1/resumes/{id} |
Update resume metadata |
| DELETE | /api/v1/resumes/{id} |
Delete resume |
| POST | /api/v1/resumes/{id}/upload |
Upload .docx |
| GET | /api/v1/resumes/{id}/download |
Download .docx |
All Jobs and Resumes endpoints require a Bearer token (Authorization: Bearer <token>).