A REST API for tracking job applications with user-scoped data, documented endpoints, automated quality checks, and a reproducible development environment.
CareerTrack API centralizes the job application workflow behind a structured HTTP interface.
It is designed as a focused backend service: authenticated, user-scoped, validated, tested, documented, and ready to run locally with Docker.
| Domain | Job application tracking |
| Interface | REST API |
| Authentication | Bearer tokens with Laravel Sanctum |
| Documentation | OpenAPI generated with Scramble |
| Local runtime | Docker Compose with PHP-FPM, Nginx, and MySQL |
| Quality gates | PHPUnit, Larastan, Laravel Pint, GitHub Actions |
Job applications are often tracked across spreadsheets, notes, inboxes, and memory.
CareerTrack API provides a small, explicit backend for that workflow:
- Keep applications in one place.
- Track status changes over time.
- Store source, salary range, notes, dates, and next steps.
- Retrieve user-scoped statistics.
- Keep each user's data isolated.
| Area | Implementation |
|---|---|
| API design | RESTful routes with JSON responses |
| Authentication | Token-based access with Laravel Sanctum |
| Authorization | Ownership enforced through policies |
| Validation | Form Requests for input rules and request authorization |
| Serialization | API Resources for stable response payloads |
| Querying | Validated filters, sorting, and pagination |
| Documentation | OpenAPI documentation generated from the Laravel app |
| Development | Dockerized PHP-FPM, Nginx, and MySQL environment |
| Quality | CI, automated tests, static analysis, and style checks |
flowchart LR
Client["Client"] --> Nginx["Nginx"]
Nginx --> Laravel["Laravel API"]
Laravel --> Auth["Sanctum"]
Laravel --> Requests["Form Requests"]
Laravel --> Policies["Policies"]
Laravel --> Controllers["Controllers"]
Controllers --> Models["Models / Scopes"]
Models --> DB["Database"]
Controllers --> Resources["API Resources"]
Resources --> Client
| Capability | Endpoint |
|---|---|
| Register user | POST /api/auth/register |
| Login user | POST /api/auth/login |
| Logout current token | POST /api/auth/logout |
- Create, list, view, update, and delete applications.
- Enforce ownership for user-specific records.
- Track status through a PHP enum.
- Store optional salary range, source, notes, location, and dates.
- Support partial updates through
PATCH.
The application listing supports validated query parameters.
| Parameter | Purpose |
|---|---|
status |
Filter by application status |
company |
Filter by company name |
from |
Filter by application date lower bound |
to |
Filter by application date upper bound |
sort_by |
Sort by allowed fields |
sort_direction |
Sort ascending or descending |
per_page |
Control page size within validation limits |
Paginated responses use Laravel's standard JSON structure:
datalinksmeta
The statistics endpoint returns authenticated-user data only.
| Metric | Description |
|---|---|
total |
Total applications for the current user |
by_status |
Count grouped by application status |
upcoming_next_steps |
Count of future next-step dates |
Requirements:
- Docker
- Docker Compose
cp env.docker.example .env
docker compose up -d
docker compose exec app composer install
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrateOpen:
http://localhost:8080
Requirements:
- PHP 8.3
- Composer
- Configured database connection
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan serveOpen:
http://localhost:8000
In addition to the Docker Compose development environment, CareerTrack API includes a production-like Kubernetes deployment.
The Kubernetes manifests were intentionally designed to follow common production practices rather than simply running Docker Compose inside Kubernetes.
The application is decomposed into independent resources, allowing each component to evolve, scale and be maintained independently.
flowchart TD
Internet["Client"]
Internet --> IC["NGINX Ingress Controller"]
IC --> ING["Ingress"]
ING --> NS["NGINX Service"]
NS --> ND["NGINX Deployment"]
ND --> PS["PHP Service"]
PS --> PD["PHP Deployment"]
PD --> MS["MySQL Service"]
MS --> STS["MySQL StatefulSet"]
STS --> PVC["Persistent Volume Claim"]
| Resource | Responsibility |
|---|---|
| Namespace | Isolates all project resources |
| Deployment | Runs the Laravel PHP-FPM application |
| Deployment | Runs the NGINX reverse proxy |
| Service | Provides internal cluster networking |
| Ingress | Exposes the application over HTTP |
| ConfigMap | Stores non-sensitive application configuration |
| Secret | Stores sensitive configuration such as credentials and application keys |
| StatefulSet | Provides stable identity for MySQL |
| PersistentVolumeClaim | Persists database data across Pod recreation |
| Job | Executes database migrations once |
Instead of deploying every component inside a single Pod, each responsibility is isolated.
This approach follows the same architectural principles commonly found in production environments.
- NGINX is responsible only for HTTP traffic.
- PHP-FPM executes the Laravel application.
- MySQL runs independently using a StatefulSet.
- Configuration is externalized through ConfigMaps and Secrets.
- Persistent storage survives Pod replacement.
- Database migrations execute independently through a Kubernetes Job.
Separating responsibilities simplifies maintenance, improves scalability and makes each component independently replaceable.
Unlike a Deployment, MySQL is deployed as a StatefulSet.
This guarantees:
- Stable Pod identity.
- Stable DNS name.
- Dedicated persistent storage.
- Safe Pod recreation.
The database storage is dynamically provisioned through a PersistentVolumeClaim.
Pod deleted
↓
StatefulSet
↓
Persistent Volume Claim
↓
Database preserved
Database migrations are not executed during container startup.
Instead, CareerTrack uses a dedicated Kubernetes Job.
This ensures that migrations execute only once and remain independent from the lifecycle of the application Pods.
Laravel Image
↓
Migration Job
↓
php artisan migrate --force
↓
Completed
Build Docker Images
↓
Push Images
↓
Deploy Kubernetes Resources
↓
Deploy MySQL StatefulSet
↓
Create Persistent Volume
↓
Deploy PHP
↓
Deploy NGINX
↓
Run Migration Job
↓
Application Available
After deployment, the application is exposed through an Ingress resource.
Example:
http://careertrack.local
The local hostname is mapped using the operating system hosts file and routed through the NGINX Ingress Controller.
Useful commands during deployment:
kubectl get pods -n careertrack
kubectl get svc -n careertrack
kubectl get ingress -n careertrack
kubectl get pvc -n careertrack
kubectl get jobs -n careertrackTo inspect logs:
kubectl logs deployment/careertrack-php -n careertrack
kubectl logs job/careertrack-migrations -n careertrack
kubectl logs careertrack-mysql-0 -n careertrackOpenAPI documentation is generated with Scramble.
| Environment | UI | JSON |
|---|---|---|
| Docker | http://localhost:8080/docs/api |
http://localhost:8080/docs/api.json |
| Local | http://localhost:8000/docs/api |
http://localhost:8000/docs/api.json |
Protected endpoints use Bearer token authentication:
Authorization: Bearer <token>| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/register |
Register a new user |
POST |
/api/auth/login |
Authenticate a user |
POST |
/api/auth/logout |
Revoke the current token |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/applications |
List authenticated user's applications |
POST |
/api/applications |
Create an application |
GET |
/api/applications/{jobApplication} |
View an application |
PUT/PATCH |
/api/applications/{jobApplication} |
Update an application |
DELETE |
/api/applications/{jobApplication} |
Delete an application |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/stats |
Get authenticated user's application statistics |
CareerTrack API follows Laravel's conventional architecture while adopting infrastructure patterns commonly found in production environments.
At the application level, responsibilities remain aligned with Laravel's core components. At the infrastructure level, the application is deployed as independent services orchestrated by Kubernetes.
This separation keeps the codebase simple while allowing the infrastructure to evolve independently.
| Layer | Responsibility |
|---|---|
| Controllers | Coordinate HTTP requests and responses |
| Form Requests | Validate and authorize incoming requests |
| Policies | Enforce ownership and authorization rules |
| Models | Encapsulate persistence, relationships and query scopes |
| API Resources | Define the public JSON representation |
| Enums | Represent bounded domain states |
| Sanctum | Authenticate API access tokens |
sequenceDiagram
participant Client
participant Route
participant Sanctum
participant FormRequest
participant Policy
participant Model
participant Resource
Client->>Route: HTTP Request
Route->>Sanctum: Authenticate
Sanctum->>FormRequest: Resolve User
FormRequest->>FormRequest: Validate Input
FormRequest->>Policy: Authorize
Policy->>Model: Allowed
Model->>Resource: Retrieve Data
Resource->>Client: JSON Response
The following diagram illustrates how an external HTTP request reaches the Laravel application once deployed on Kubernetes.
flowchart LR
Client["🌐 Client"]
Ingress["NGINX Ingress"]
NginxSvc["NGINX Service"]
Nginx["NGINX Pod"]
PhpSvc["PHP Service"]
Laravel["Laravel PHP Pod"]
MysqlSvc["MySQL Service"]
Mysql["MySQL StatefulSet"]
Client --> Ingress
Ingress --> NginxSvc
NginxSvc --> Nginx
Nginx --> PhpSvc
PhpSvc --> Laravel
Laravel --> MysqlSvc
MysqlSvc --> Mysql
The infrastructure keeps networking, application execution and persistence separated into dedicated Kubernetes resources.
This mirrors the architecture commonly used by modern backend applications deployed in container orchestration platforms.
| Component | Files |
|---|---|
| API controllers | app/Http/Controllers/Api |
| Request validation | app/Http/Requests |
| Response resources | app/Http/Resources |
| Authorization | app/Policies/JobApplicationPolicy.php |
| Domain state | app/Enums/ApplicationStatus.php |
| Persistence | app/Models/JobApplication.php, app/Models/User.php |
| Routes | routes/api.php |
CareerTrack API was intentionally designed around well-established engineering principles instead of choosing technologies arbitrarily.
The following decisions were made to improve maintainability, scalability and operational reliability.
Laravel provides a mature ecosystem for building REST APIs while encouraging clear separation of responsibilities.
The project intentionally relies on Laravel's native features before introducing additional abstraction.
Examples include:
- Form Requests for validation.
- Policies for authorization.
- API Resources for serialization.
- Sanctum for authentication.
- Eloquent Scopes for reusable query logic.
This keeps the codebase simple, readable and aligned with framework conventions.
Docker provides a reproducible development environment.
Every developer works with the same versions of PHP, MySQL and NGINX without relying on local machine configuration.
The Docker environment mirrors the application stack while keeping local setup simple.
Docker solves containerization.
Kubernetes solves orchestration.
The project includes Kubernetes manifests to demonstrate how a Laravel application can be deployed using production-oriented infrastructure concepts such as Deployments, Services, Ingress, StatefulSets and Jobs.
The goal was not simply to "run Laravel on Kubernetes", but to model the responsibilities of each infrastructure component independently.
NGINX and PHP-FPM perform different responsibilities.
Separating them allows each component to evolve independently.
Benefits include:
- Independent scaling.
- Clear separation of concerns.
- Easier troubleshooting.
- Architecture closer to production environments.
Databases require stable identity and persistent storage.
Unlike a Deployment, a StatefulSet guarantees:
- Stable Pod names.
- Stable DNS.
- Dedicated storage.
- Ordered startup and shutdown.
Using a StatefulSet makes the persistence layer significantly more reliable.
Containers are ephemeral.
If the database stored its files inside the container filesystem, deleting the Pod would also delete the database.
A PersistentVolumeClaim decouples storage from the container lifecycle, ensuring that data survives Pod recreation.
Configuration should not be hardcoded inside container images.
CareerTrack separates configuration into:
| Resource | Purpose |
|---|---|
| ConfigMap | Non-sensitive configuration |
| Secret | Credentials, passwords and application keys |
This allows the same container image to be deployed across different environments without modification.
Database migrations should not execute every time an application Pod starts.
Instead, CareerTrack executes migrations through a dedicated Kubernetes Job.
This approach offers several advantages:
- Runs once.
- Independent from application Pods.
- Easier troubleshooting.
- Better operational control.
- Closer to production deployment practices.
The project includes automated tests to verify observable API behavior.
The objective is to detect regressions before changes are merged.
The test suite covers:
- Authentication.
- Authorization.
- CRUD operations.
- Validation.
- Filtering.
- Pagination.
- Statistics.
- Documentation availability.
Static analysis helps identify potential issues before runtime.
Larastan improves confidence by detecting:
- Invalid types.
- Incorrect method calls.
- Unreachable code.
- Common programming mistakes.
This complements, rather than replaces, automated testing.
CareerTrack favors explicit solutions over unnecessary complexity.
The project intentionally follows Laravel conventions while adopting infrastructure practices commonly found in modern backend systems.
The result is a codebase that is:
- Easy to understand.
- Easy to deploy.
- Easy to test.
- Easy to extend.
- Representative of real-world backend development.
The test suite uses PHPUnit with Laravel's testing tools.
During tests, the project uses SQLite in memory for fast, isolated execution.
| Area | Covered behavior |
|---|---|
| Authentication | Register, login, failed login, logout, guest protection |
| Applications CRUD | Create, list, view, update, delete |
| Ownership | Users cannot access or mutate other users' applications |
| Validation | Required fields, enum status, salary rules, date rules, partial PATCH |
| Filtering | Status, company, sorting, pagination, invalid query parameters |
| Statistics | User-scoped totals, status counts, upcoming next steps |
| Documentation | OpenAPI UI and JSON availability |
Before changes are integrated, GitHub Actions runs:
composer analyse
vendor/bin/pint --test
php artisan testflowchart LR
PR["Push / Pull Request"] --> Setup["Install dependencies"]
Setup --> Env["Prepare Laravel environment"]
Env --> Tests["PHPUnit"]
Tests --> Style["Pint"]
Style --> Static["Larastan"]
Static --> Ready["Ready for review"]
| Task | Command |
|---|---|
| Start services | docker compose up -d |
| View services | docker compose ps |
| Run migrations | docker compose exec app php artisan migrate |
| Run tests | docker compose exec app php artisan test |
| Run static analysis | docker compose exec app composer analyse |
| Check formatting | docker compose exec app vendor/bin/pint --test |
| Task | Command |
|---|---|
| Run migrations | php artisan migrate |
| Run tests | php artisan test |
| List routes | php artisan route:list |
| Run static analysis | composer analyse |
| Check formatting | vendor/bin/pint --test |
| Format code | vendor/bin/pint |
app/
Enums/
Http/
Controllers/Api/
Requests/
Resources/
Models/
Policies/
config/
scramble.php
sanctum.php
database/
factories/
migrations/
seeders/
docker/
nginx/
php/
routes/
api.php
tests/
Feature/
Auth/
Documentation/
JobApplications/
Stats/
Unit/
.github/
workflows/
| File | Purpose |
|---|---|
.env.example |
Default local Laravel environment |
env.docker.example |
Docker-oriented local environment |
| Service | Responsibility |
|---|---|
app |
PHP 8.3 FPM, Composer, Laravel runtime |
nginx |
Serves the Laravel public directory |
mysql |
MySQL 8 database with persistent volume and healthcheck |
| Resource | Purpose |
|---|---|
| Namespace | Project isolation |
| Deployments | PHP and NGINX |
| StatefulSet | MySQL |
| Services | Internal networking |
| ConfigMap | Configuration |
| Secret | Sensitive configuration |
| Job | Database migration |
| Ingress | External access |
CareerTrack is designed around the principle of separating application logic from infrastructure concerns.
The same Laravel application can be executed in two different environments:
| Environment | Purpose |
|---|---|
| Local Development | Docker Compose |
| Production-like Development | Kubernetes |
Both environments share the same application code while using different orchestration layers.
Docker Compose provides a fast and predictable local development environment.
It includes:
- PHP-FPM
- NGINX
- MySQL
This environment prioritizes developer productivity and simplicity.
The Kubernetes deployment mirrors a production-oriented architecture.
Responsibilities are intentionally separated across dedicated resources.
| Component | Kubernetes Resource |
|---|---|
| Laravel | Deployment |
| NGINX | Deployment |
| MySQL | StatefulSet |
| Configuration | ConfigMap |
| Secrets | Secret |
| Networking | Services |
| External Access | Ingress |
| Database Initialization | Job |
The infrastructure follows several design principles.
- Stateless application containers.
- Persistent database storage.
- Externalized configuration.
- Independent HTTP layer.
- Infrastructure as Code.
- Reproducible deployments.
- Separation of responsibilities.
These principles make the project easier to maintain and closer to modern backend deployment practices.
CareerTrack is intentionally designed as an evolving project.
Potential future enhancements include:
- Refresh token support.
- API versioning.
- Rate limiting improvements.
- Background queues.
- Event-driven notifications.
- Helm Chart.
- Horizontal Pod Autoscaler.
- Horizontal scaling.
- Redis cache.
- External object storage.
- GitOps deployment with Argo CD.
- Multi-stage production Docker images.
- Image vulnerability scanning.
- Kubernetes health monitoring.
- Prometheus metrics.
- Grafana dashboards.
- Mutation testing.
- Contract testing.
- Performance testing.
- Security scanning.
CareerTrack API started as a REST API for managing job applications and gradually evolved into a production-oriented backend project used to explore modern software engineering practices, containerization and Kubernetes deployment.
The project combines several areas commonly expected in modern software engineering roles:
- Laravel backend development.
- REST API design.
- Authentication and authorization.
- Automated testing.
- Static analysis.
- Docker.
- Kubernetes.
- Infrastructure design.
- CI-oriented workflows.
- Technical documentation.
Rather than maximizing feature count, the primary objective is to demonstrate sound engineering practices, maintainable architecture and production-oriented deployment techniques.
The project continues to evolve as new technologies and engineering practices are incorporated.
Use a pull request workflow.
flowchart LR
Branch["Feature Branch"] --> PR["Pull Request"]
PR --> CI["CI"]
CI --> Review["Review"]
Review --> Merge["Merge"]
- Create a focused feature branch.
- Keep changes small and reviewable.
- Open a pull request.
- Wait for CI to pass.
- Address review feedback.
- Merge after approval.
CareerTrack API is intentionally small, explicit, and framework-aligned.
The project favors:
- Clear HTTP behavior over hidden abstraction.
- Laravel conventions over unnecessary patterns.
- User data isolation as a first-class requirement.
- Tests that protect observable API behavior.
- Documentation and automation that reduce maintenance cost.
This project is open-sourced software licensed under the MIT license.