A modern SaaS platform for tracking job applications, interviews, tasks, and career opportunities.
Stop losing track of your job search across spreadsheets and inboxes — manage your entire pipeline, from wishlist to offer, in one professional workspace.
The dashboard, captured from the running application with seeded demo data.
- Why CareerFlow
- Features
- Tech stack
- Quick start
- Architecture
- Data model (ERD)
- Request lifecycle
- Project structure
- Development
- Testing & quality
- Security
- Reliability & observability
- Deployment
- Documentation
- Roadmap
- Known limitations
- Future improvements
- License
A typical active job search runs 20–80 applications over several months. The information that matters — what stage each application is at, when the next interview is, who you spoke to, what follow-up is due — ends up scattered and forgotten. CareerFlow centralizes it into a single source of truth and turns it into something you can actually reason about, with a pipeline board, reminders, and analytics on what's converting.
One link, every platform: app.careerflow.app/install — the page detects your device and points you at the App Store, Google Play, or the PWA install prompt.
Live demo on the web: frontend-jade-two-zfchqjb5ws.vercel.app — open it and press ▶ Try demo — no signup needed on the login screen.
Native iOS and Android apps build from the same codebase via Capacitor — see
docs/app-store-submission.mdfor how to ship them.
Want your own copy? Deploy the full stack (API + Postgres + web) with a single click:
| Button | What it provisions |
|---|---|
| Render | Backend (Docker) + managed Postgres + static frontend, all from render.yaml. |
| Heroku | Backend (container stack) + Heroku Postgres add-on, from app.json. |
| Vercel | Frontend only (point VITE_API_BASE_URL at any CareerFlow API). |
The seed routine creates a demo account on first boot — demo@careerflow.app / DemoPass123! — pre-wired to the Try-demo button.
CareerFlow is a Progressive Web App. From any modern browser:
- Chrome / Edge — click the install icon (⊕) in the address bar, or Menu → Install CareerFlow.
- Safari (iOS / iPadOS) — Share → Add to Home Screen.
- Firefox (Android) — Menu → Install.
It then launches in its own window, works offline for the shell, and shows up alongside your native apps. No store, no download.
- 🔐 Secure auth — email/password registration, bcrypt hashing, short-lived JWT access tokens with refresh-token rotation, and fully user-scoped data.
- 🏢 Companies — CRUD with search, industry filtering, and pagination.
- 💼 Application pipeline — eight stages (Wishlist → Applied → Assessment → Interview → Final → Offer → Rejected → Accepted), shown as a Kanban board or a sortable list, with salary, location, source, and links.
- 🗓️ Interviews — multiple rounds per application plus a dedicated cross-pipeline list filtered by upcoming/past.
- 💰 Offers — track base salary, bonus, equity, benefits, and your decision (pending/negotiating/accepted/declined).
- ✅ Tasks — priorities, due dates, completion, and semantic priority sorting.
- 📝 Notes — Markdown notes per application.
- 📎 Attachments — secure resume/cover-letter uploads (validated, owner-only download).
- 📊 Dashboard & analytics — headline stats, upcoming interviews, pending tasks, and Recharts visualizations (applications over time, status & industry distribution, conversion rates).
- 📤 CSV export — download your whole pipeline as an RFC-4180 CSV (Excel/Sheets-ready) in one click.
- 📁 Document vault — store multiple CVs (PDF/DOCX), certificates, and skills; durable object storage (Cloudflare R2 / S3) with a local-disk fallback.
- ✨ AI CV tailoring — rewrite a CV (and optional cover letter) for a specific job via OpenAI (gpt-4o-mini), saved as a new versioned CV. Falls back to an offline stub with no API key.
- 🔎 Job search — save search filters and fetch matching jobs from Adzuna (with a mock provider when no keys are set); tailor a CV straight from a fetched job.
- ⚙️ Settings — update your profile and change your password.
- 📱 Installable PWA + mobile UI — install to a phone/desktop home screen; on small screens the sidebar becomes a slide-in drawer.
- 🌗 Polished UX — responsive layout, light/dark themes, Zod-validated forms, and proper loading / empty / error states throughout.
Captured from the running app (
frontend/scripts/screenshot.mjs) against the seeded demo data.
| Dashboard | Pipeline board |
|---|---|
![]() |
![]() |
| Application detail | Analytics |
![]() |
![]() |
| Offers | Interviews |
![]() |
![]() |
On phones the sidebar collapses into a slide-in drawer — the app installs to your home screen as a PWA.
| Layer | Technologies |
|---|---|
| Frontend | React 18, TypeScript, Vite, React Router, TanStack Query, Recharts |
| Backend | Python 3.11/3.12, FastAPI, SQLAlchemy 2.0, Alembic, Pydantic v2 |
| Database | PostgreSQL 16 |
| Auth | JWT (PyJWT), bcrypt |
| DevOps | Docker, Docker Compose, Nginx, GitHub Actions |
| Quality | Pytest, Vitest, Ruff, mypy, ESLint, Prettier, Bandit, pip-audit |
The entire platform runs with one command (requires Docker):
git clone <repo-url> careerflow
cd careerflow
docker compose up --build| Service | URL |
|---|---|
| Web app | http://localhost:5173 |
| API (Swagger UI) | http://localhost:8000/api/docs |
| API (ReDoc) | http://localhost:8000/api/redoc |
| Health probe | http://localhost:8000/health |
The backend applies migrations and seeds the demo account automatically on first start.
Three tiers: a React SPA, a layered FastAPI backend, and PostgreSQL. In production Nginx serves
the built SPA and reverse-proxies /api to the backend, so the browser sees a single origin.
flowchart LR
subgraph Browser
SPA["React SPA<br/>Vite · TanStack Query · Recharts"]
end
subgraph Edge["Edge (production)"]
NGINX["Nginx<br/>static assets + /api proxy"]
end
subgraph Backend["FastAPI backend"]
direction TB
ROUTERS["api/ routers<br/>(HTTP only)"]
SERVICES["services/<br/>(business rules + authz)"]
REPOS["repositories/<br/>(all SQL)"]
MODELS["models/<br/>(SQLAlchemy 2.0)"]
ROUTERS --> SERVICES --> REPOS --> MODELS
end
DB[("PostgreSQL")]
FILES[["Local file storage<br/>(off web root)"]]
SPA -->|HTTPS / JSON| NGINX
NGINX -->|/api/v1| ROUTERS
NGINX -->|static| SPA
MODELS --> DB
SERVICES --> FILES
The backend enforces a strict, inward-pointing dependency rule — routers do HTTP, services hold business logic and authorization, repositories own all data access. Details and rationale live in docs/02-technical-architecture.md.
erDiagram
USERS ||--o{ COMPANIES : owns
USERS ||--o{ APPLICATIONS : owns
USERS ||--o{ TASKS : owns
COMPANIES ||--o{ APPLICATIONS : "linked to (nullable)"
APPLICATIONS ||--o{ INTERVIEWS : has
APPLICATIONS ||--o{ NOTES : has
APPLICATIONS ||--o{ ATTACHMENTS : has
APPLICATIONS |o--o{ TASKS : "may relate to"
USERS {
uuid id PK
string email UK
string hashed_password
bool is_active
}
COMPANIES {
uuid id PK
uuid user_id FK
string name
string industry
datetime deleted_at
}
APPLICATIONS {
uuid id PK
uuid user_id FK
uuid company_id FK
string role_title
enum status
int salary_min
datetime deleted_at
}
INTERVIEWS {
uuid id PK
uuid application_id FK
datetime scheduled_at
enum result
}
TASKS {
uuid id PK
uuid application_id FK
string title
bool is_completed
}
NOTES {
uuid id PK
uuid application_id FK
text body
}
ATTACHMENTS {
uuid id PK
uuid application_id FK
enum kind
}
Full schema, indexes, and referential rules: docs/03-database-design.md.
Creating an application, showing validation, authorization, and the layer hand-off:
sequenceDiagram
autonumber
participant B as Browser (SPA)
participant R as Router (api/)
participant S as ApplicationService
participant Repo as Repositories
participant DB as PostgreSQL
B->>R: POST /api/v1/applications (Bearer JWT)
R->>R: Validate body (Pydantic) + decode JWT
R->>S: create(owner, data)
opt company_id provided
S->>Repo: company owned by user?
Repo->>DB: SELECT company WHERE id AND user_id
Repo-->>S: Company or NotFound(404)
end
S->>Repo: add(application)
Repo->>DB: INSERT application
S-->>R: Application
R-->>B: 201 Created (ApplicationRead)
The authentication flow is diagrammed in docs/diagrams/sequence-auth.mmd.
careerflow/
├── backend/ FastAPI service (layered: api · services · repositories · models)
│ ├── app/
│ ├── alembic/ database migrations
│ └── tests/ unit + integration tests
├── frontend/ React + TypeScript SPA (Vite)
│ └── src/ pages · components · hooks · services · contexts
├── docs/ PRD, architecture, DB & API design, security, diagrams
├── .github/workflows/ CI pipeline
└── docker-compose.yml full-stack one-command startup
A deeper tour is in docs/06-folder-structure.md.
Run the apps natively for a tighter feedback loop.
Backend (Python ≥ 3.11):
cd backend
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
cp .env.example .env
alembic upgrade head
uvicorn app.main:app --reloadFrontend (Node ≥ 20):
cd frontend
npm install
npm run dev # http://localhost:5173, proxies /api to the backendEvery change is gated by the same checks CI runs.
# Backend
cd backend
ruff check . && ruff format --check . && mypy app
bandit -r app && pip-audit
pytest --cov --cov-report=term-missing # ~97% line coverage
# Frontend
cd frontend
npm run lint && npm run typecheck && npm test && npm run buildThe backend suite (83 tests) runs against in-memory SQLite for speed and isolation; the production target is PostgreSQL. CI enforces a minimum 80% backend coverage and builds both Docker images.
CareerFlow was built with a security-first mindset and audited in a dedicated phase:
- All data is user-scoped; cross-user access returns
404(no existence leak). - Passwords are bcrypt-hashed; auth errors are generic to prevent enumeration.
- Inputs are validated by Pydantic and persisted via bound parameters (no SQL injection).
- Uploads are type/size-validated, stored off the web root with opaque names, and served only to their owner.
- Rate limiting on the auth endpoints (
/auth/login,/auth/refresh,/auth/change-password) via SlowAPI, returning429withRetry-AfterandX-RateLimit-*headers. banditandpip-auditrun in CI and are currently clean.
See docs/05-security-design.md and the audit in docs/security-review.md.
- Global error boundary — render-time exceptions show a recoverable fallback ("Reload app") instead of a blank screen, and are reported to Sentry.
- Loading skeletons — board, table, and detail views render shape-matching skeletons while data loads, keeping layout stable.
- Toast notifications — non-blocking success/error toasts on login, profile and password changes, and offer/interview actions.
- Structured audit logging — security-relevant actions (logins, password
changes, profile updates, offer decisions) emit one JSON line each with
timestamp,
user_id,action, andstatusto a dedicatedcareerflow.auditlogger. - Health probe —
GET /healthreports database connectivity, app version, and uptime, and returns503when the database is unreachable. - Error monitoring (Sentry) — optional, env-gated on both the API
(
SENTRY_DSN) and the SPA (VITE_SENTRY_DSN); a no-op when unset.
The repository is container-first. docker compose up --build is production-shaped: the backend
image runs migrations on start, and Nginx serves the built SPA while proxying the API. For a real
deployment, override the defaults in a root .env (see .env.example) — at minimum a
strong JWT_SECRET and managed Postgres credentials; the app refuses to start in production with a
placeholder secret.
| Document | Contents |
|---|---|
| Product Requirements | Problem, personas, journeys, functional & non-functional requirements |
| Technical Architecture | Layering, technology rationale, boundaries |
| Database Design | Tables, indexes, soft-delete & referential rules |
| API Design | Endpoints, pagination, error format, examples |
| Security Design / Security Review | Threat model, controls, audit findings |
| Deployment Guide | Hosting the frontend, backend, and database |
| Runtime Verification | Evidence from actual builds and runs |
| Folder Structure | Repository layout and conventions |
| Backend Review / Repository Audit | Self-reviews and refactors |
Interactive API docs are generated from the code at /api/docs (Swagger) and /api/redoc.
- Core domain: companies, applications, interviews, tasks, notes, attachments
- Dashboard & analytics
- Containerized stack + CI
- Drag-and-drop status changes on the board
- Email/calendar reminders for upcoming interviews
- CSV/JSON export of the pipeline
- Saved views and bulk actions
- Single-user accounts only (no shared workspaces or collaboration).
- Editing an application from its detail page lists only its current company in the picker; reassignment is available from the create flow.
- Attachments are stored on local disk (a volume in Compose) rather than object storage.
- The board groups rejected applications outside the visible pipeline columns (they remain in the list view).
- Refresh-token rotation and token revocation.
- Pluggable object storage (S3-compatible) for attachments.
- Full-text search across notes and applications.
- Optimistic UI updates and drag-and-drop on the pipeline board.
MIT © 2026 CareerFlow






