Developer: Kaysarul Anas
Technology Stack: Django REST Framework + React (Vite)
Report Date: February 10, 2026
Project Status: Active Development
The JobTrackerr-FullStack-React-Django is a comprehensive Full-Stack Cross-Platform Solution designed to help job seekers organize and manage their job applications efficiently. The system consists of a robust Django backend, a modern React web frontend, and a native mobile application built with React Native. It provides secure user authentication, real-time synchronization across devices, file management, and insightful analytics.
- ✅ Cross-Platform: Web (React) and Mobile (React Native) applications
- ✅ Secure Auth: JWT-based authentication with Google OAuth integration
- ✅ Cloud Native: Backend on AWS EC2, Frontend on Vercel
- ✅ User Experience: Seamless auto-login and optional email verification flows
- ✅ Data Integrity: PostgreSQL production database with robust API
- ✅ Modern UI: Tailwind CSS v4 (Web) and Native Styling (Mobile)
- ✅ File System: Secure multi-file upload management
- Problem Statement
- My Solution
- System Architecture
- Backend Development
- Frontend Development
- UI/UX Design
- Authentication & Security
- Database Design
- API Documentation
- Deployment
- Testing
As a job seeker myself, I noticed several pain points:
- Disorganization: Difficulty tracking multiple applications across different companies
- Lost Information: Forgetting which resume version was sent where
- Missed Follow-ups: Losing track of application statuses and interview dates
- Scattered Data: Job descriptions and notes stored in various locations
- No Analytics: Unable to see patterns in my application success rates
I designed this for:
- Recent graduates entering the job market
- Career changers managing multiple applications
- Active job seekers applying to 10+ positions simultaneously
- Anyone who wants organized job search records
- Email/password registration and login
- Google OAuth integration for quick access
- JWT token-based session management
- Automatic token refresh mechanism
- Track company name, position, and application status
- Record application dates and deadlines
- Store job post URLs for easy reference
- Save job requirements for interview preparation
- Add personal notes and observations
- Upload multiple files per application (resumes, cover letters, portfolios)
- Track which version of documents was sent to each company
- Secure file storage with user-specific access control
- Visual statistics showing application status breakdown
- Board view (Kanban-style) for organization
- List view for detailed information display
- Search and filter capabilities
- Demo mode for exploring features without login
- Submit reviews and ratings
- Public testimonials for homepage
- Moderation system for review approval
graph TB
subgraph "Client Layer"
A["Web Application<br/>React + Tailwind v4<br/>(Hosted on Vercel)"]
B["Mobile Application<br/>React Native<br/>(iOS & Android)"]
end
subgraph "API Gateway & Security"
C[Nginx<br/>Reverse Proxy]
D[Gunicorn<br/>WSGI Server]
E[SSL/TLS<br/>Encryption]
end
subgraph "Backend Layer (AWS EC2)"
F[Django REST Framework<br/>API Logic]
G[Authentication System<br/>SimpleJWT + Allauth]
H[Business Logic<br/>Apps & Views]
end
subgraph "Data & Storage"
I[PostgreSQL<br/>Production DB]
J[File System<br/>Resume/Cover Letter Storage]
end
A <-->|HTTPS/JSON| C
B <-->|HTTPS/JSON| C
C <--> D
D <--> F
F --> G
F --> H
H --> I
H --> J
style A fill:#61dafb,stroke:#333,stroke-width:2px
style B fill:#61dafb,stroke:#333,stroke-width:2px
style F fill:#092e20,stroke:#333,stroke-width:2px,color:#fff
style I fill:#336791,stroke:#333,stroke-width:2px,color:#fff
| Layer | Technology | Why I Chose It |
|---|---|---|
| Frontend | React 19.2 | Modern, component-based UI |
| Vite 7.2 | Fast dev server and builds | |
| Tailwind CSS v4 | Rapid styling with utilities | |
| React Router v7 | Robust client-side routing | |
| Axios | Clean API integration | |
| Backend | Django 5.2 | Batteries-included framework |
| Django REST Framework 3.16 | Powerful API toolkit | |
| SimpleJWT 5.5 | Industry-standard auth | |
| Django Allauth 65.14 | Easy social auth | |
| Database | SQLite | Simple for development |
| PostgreSQL | Scalable for production | |
| DevOps | Gunicorn | Production WSGI server |
| WhiteNoise | Efficient static files | |
| Docker | Containerization |
I built the backend with Django 5.2 and Django REST Framework 3.16 for a robust, scalable API.
# requirements.txt
Django==5.2.11
djangorestframework==3.16.1
djangorestframework_simplejwt==5.5.1
django-allauth==65.14.0
django-cors-headers==4.9.0
dj-rest-auth==7.0.2
gunicorn==23.0.0
psycopg2-binary==2.9.10
dj-database-url==2.3.0
whitenoise==6.9.0
python-dotenv==1.2.1The core model for tracking job applications:
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Application(models.Model):
# Status choices
APPLIED = 'Applied'
INTERVIEW = 'Interview'
OFFER = 'Offer'
REJECTED = 'Rejected'
STATUS_CHOICES = [
(APPLIED, 'Applied'),
(INTERVIEW, 'Interview'),
(OFFER, 'Offer'),
(REJECTED, 'Rejected'),
]
# Relationships
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="applications"
)
# Core fields
company_name = models.CharField(max_length=255)
position_title = models.CharField(max_length=255)
notes = models.TextField(blank=True, null=True)
# Job details
job_post_url = models.URLField(blank=True, null=True)
job_requirements = models.TextField(blank=True, null=True)
# Status tracking
status = models.CharField(
max_length=50,
choices=STATUS_CHOICES,
default=APPLIED
)
applied_at = models.DateField(blank=True, null=True)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.company_name} - {self.position_title}"My Design Decisions:
- ✅ User foreign key ensures data isolation
- ✅ Status choices provide consistent data
- ✅ Timestamps track creation and updates
- ✅ Optional fields allow flexibility
I created this to handle multiple file uploads per application:
class ApplicationFile(models.Model):
application = models.ForeignKey(
Application,
on_delete=models.CASCADE,
related_name="files"
)
file = models.FileField(upload_to="application_files/")
file_type = models.CharField(max_length=50)
original_filename = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.original_filenameFor user feedback and testimonials:
class Review(models.Model):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="reviews"
)
rating = models.IntegerField(default=5) # 1-5 stars
comment = models.TextField()
is_public = models.BooleanField(default=False) # For moderation
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"Review by {self.user.username} - {self.rating} stars"erDiagram
USER ||--o{ APPLICATION : creates
USER ||--o{ REVIEW : writes
APPLICATION ||--o{ APPLICATION_FILE : contains
USER {
int id PK
string username
string email
string password
datetime date_joined
}
APPLICATION {
int id PK
int user_id FK
string company_name
string position_title
text notes
string job_post_url
text job_requirements
string status
date applied_at
datetime created_at
datetime updated_at
}
APPLICATION_FILE {
int id PK
int application_id FK
file file
string file_type
string original_filename
datetime created_at
}
REVIEW {
int id PK
int user_id FK
int rating
text comment
boolean is_public
datetime created_at
datetime updated_at
}
I used ViewSets for clean CRUD operations:
from rest_framework import viewsets, permissions
class ApplicationViewSet(viewsets.ModelViewSet):
serializer_class = ApplicationSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
# Only return applications belonging to current user
return self.request.user.applications.all()
def perform_create(self, serializer):
# Automatically set user to current logged-in user
serializer.save(user=self.request.user)Security Features I Added:
- ✅ Authentication required
- ✅ User-specific data filtering
- ✅ Automatic user assignment
- ✅ No cross-user data access
I chose modern React ecosystem tools for optimal developer experience:
{
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.13.0",
"axios": "^1.13.4",
"@react-oauth/google": "^0.13.4",
"lucide-react": "^0.563.0"
},
"devDependencies": {
"vite": "^7.2.4",
"tailwindcss": "^4.1.18",
"@tailwindcss/vite": "^4.1.18",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1"
}
}I organized my components into:
src/
├── components/
│ ├── AuthLayout.jsx # Auth page wrapper
│ ├── Navbar.jsx # Navigation
│ ├── Footer.jsx # Footer
│ ├── LandingHero.jsx # Homepage hero
│ ├── Features.jsx # Feature showcase
│ ├── Testimonials.jsx # User reviews
│ └── dashcomp/ # Dashboard components
│ ├── DashboardHeader.jsx
│ ├── StatsSection.jsx
│ ├── BoardView.jsx
│ ├── ListView.jsx
│ ├── ApplicationCard.jsx
│ ├── SearchControls.jsx
│ └── ReviewForm.jsx
├── context/
│ └── AuthContext.jsx # Authentication state
├── pages/
│ ├── HomePage.jsx
│ ├── LoginPage.jsx
│ ├── SignupPage.jsx
│ └── Dashboard/
│ └── DashBoard.jsx
└── api/
└── axios.js # API client
I configured Axios with interceptors for automatic token management:
// src/api/axios.js
import axios from 'axios';
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000/',
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor: Attach JWT token
api.interceptors.request.use(
(config) => {
const authEndpoints = ['/api/auth/token/', '/api/auth/registration/'];
const isAuthEndpoint = authEndpoints.some(endpoint =>
config.url.includes(endpoint)
);
if (!isAuthEndpoint) {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
},
(error) => Promise.reject(error)
);
export default api;I extended the platform to mobile devices using React Native and Expo, sharing the same backend API as the web version.
{
"dependencies": {
"react-native": "0.76.6",
"expo": "~52.0.25",
"expo-router": "~4.0.16",
"axios": "^1.7.9",
"lucide-react-native": "^0.474.0",
"react-native-safe-area-context": "4.12.0",
"react-native-gesture-handler": "~2.20.2",
"expo-linear-gradient": "~14.0.2"
}
}- Status: Currently in final development phase.
- Teaser Banner: Interactive landing page banner with Android slideshow.
- Navigation: Stack and Tab navigation using
expo-router - Gestures: Smooth transitions and touch interactions
- Safe Area: Optimized for iPhone Dynamic Island and notches
- Status Bar: Adaptive styling for dark/light modes
- Secure Storage: JWT tokens stored in
AsyncStorage - Auto-Login: Token validation on app launch
- Profile Modal: Integrated user management within the app
- Real-time Stats: View application counts and statuses
- Infinite Scroll: Efficiently browse long lists of applications
- Pull-to-Refresh: Update data with a simple gesture
mobile/
├── src/
│ ├── api/ # API configuration (Axios)
│ ├── assets/ # Images and fonts
│ ├── components/ # Reusable UI components
│ │ ├── ProfileContent.js
│ │ └── ...
│ ├── context/ # Auth & Theme Contexts
│ ├── navigation/ # Stack & Tab Navigators
│ └── screens/ # Application screens
│ ├── DashboardScreen.js
│ ├── LoginScreen.js
│ └── SignupScreen.js
├── App.js # Entry point
└── app.json # Expo configuration
I focused on:
- Minimalism: Clean, uncluttered interfaces
- Responsiveness: Mobile-first design approach
- Accessibility: Semantic HTML and ARIA labels
- Performance: Optimized assets and lazy loading
- Consistency: Unified color scheme and typography
I use Tailwind CSS v4 with custom configuration:
/* src/index.css */
@import "tailwindcss";
@theme {
--font-sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
body {
@apply font-sans;
}Color Usage:
- Primary: Blue tones for CTAs and interactive elements
- Success: Green for positive actions (Offer status)
- Warning: Yellow/Orange for pending states (Interview)
- Danger: Red for rejections and destructive actions
- Neutral: Gray scale for text and backgrounds
I implemented a dual-authentication system:
- Email/Password: Traditional credentials-based auth
- Google OAuth: Social authentication for convenience
Access Token:
- Lifetime: 60 minutes
- Storage: localStorage
- Usage: Attached to all API requests
Refresh Token:
- Lifetime: 1 day
- Storage: localStorage
- Usage: Obtain new access token when expired
Every API endpoint I created implements user-based filtering:
def get_queryset(self):
return self.request.user.applications.all()This ensures users can only access their own data.
During development, I encountered and resolved several issues:
Problem: Backend rejected valid registration data
Solution: Added password2 field for confirmation
Problem: Django Sites framework misconfiguration
Solution: Corrected SITE_ID and linked Social Application
Problem: Axios interceptor attached tokens to login requests
Solution: Excluded auth endpoints from token attachment
Problem: Google Login blocked in production by browser security
Solution: Removed conflicting same-origin-allow-popups headers from vercel.json and set SECURE_CROSS_ORIGIN_OPENER_POLICY = None in Django.
Problem: Cloud Admin panel rejected all login attempts
Solution: Added the API domain to CSRF_TRUSTED_ORIGINS.
My database consists of 4 main tables:
- User (Django built-in)
- Application (Custom)
- ApplicationFile (Custom)
- Review (Custom)
I use Django's migration system:
# Create migrations
python manage.py makemigrations
# Apply migrations
python manage.py migrate- Development:
http://127.0.0.1:8000/ - Production:
https://api.jobtracker.kaysarulanas.me/
Obtain JWT access and refresh tokens.
Request:
{
"email": "user@example.com",
"password": "securepassword"
}Response:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGc..."
}List all applications for authenticated user.
Create new application.
Request:
{
"company_name": "Microsoft",
"position_title": "Frontend Developer",
"status": "Applied",
"applied_at": "2026-02-08",
"notes": "Applied through LinkedIn"
}Update application.
Delete application.
- Backend: Hosted on AWS EC2 (Ubuntu) using Gunicorn as the application server and Nginx as a reverse proxy.
- Frontend: Deployed on Vercel with global CDN distribution.
- Database: PostgreSQL (Production) hosted on AWS/RDS or similar provider.
- Domain: Custom domain
jobtracker.kaysarulanas.mewith SSL via Let's Encrypt.
- EC2 Instance: t2.micro (Free Tier), Ubuntu 22.04 LTS
- Security Groups: Allow HTTP (80), HTTPS (443), SSH (22)
- Database: PostgreSQL installed and secured
Gunicorn Systemd Service:
[Unit]
Description=gunicorn daemon
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/JobTrackerr-FullStack-React-Django/backend
ExecStart=/home/ubuntu/JobTrackerr-FullStack-React-Django/backend/venv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
backend.wsgi:applicationNginx Configuration:
- Reverse proxy to Gunicorn socket
- Static file serving (
/static/) - SSL termination (Certbot/Let's Encrypt)
- CORS headers for Vercel frontend
- Build Command:
vite build - Output Directory:
dist - Environment Variables:
VITE_API_URL: Points to https://api.jobtracker.kaysarulanas.meVITE_GOOGLE_CLIENT_ID: Production OAuth credentials
- Expo Application Services (EAS):
eas build -p android: Generates APK/AAB for Androideas build -p ios: Generates IPA for iOS (requires Apple Developer Account)
- Over-the-Air Updates: Push JavaScript updates directly to users via
eas update
I plan to implement:
- Unit tests for all models
- API endpoint tests
- Authentication flow tests
- Component unit tests
- Integration tests
- E2E tests with Playwright
# applications/tests.py
from django.test import TestCase
from .models import Application
class ApplicationModelTest(TestCase):
def test_create_application(self):
app = Application.objects.create(
user=self.user,
company_name='Test Corp',
position_title='Developer',
status='Applied'
)
self.assertEqual(str(app), 'Test Corp - Developer')I successfully built a full-stack job application tracker that demonstrates:
✅ Full-stack Development
- Modern React frontend with Tailwind CSS v4
- Robust Django REST Framework backend
- Secure JWT authentication with OAuth integration
✅ Software Engineering Best Practices
- RESTful API design
- Component-based architecture
- Database normalization
- Security-first approach
✅ User-Centric Design
- Intuitive dashboard interface
- Responsive mobile design
- Demo mode for exploration
Backend:
- Django ORM and migrations
- DRF serializers and viewsets
- JWT authentication
- Social OAuth integration
- File upload handling
Frontend:
- React hooks and context
- React Router navigation
- Axios interceptors
- Tailwind CSS styling
- Component composition
DevOps:
- Docker containerization
- Environment configuration
- Static file serving
- CORS configuration
This project is part of my portfolio demonstrating full-stack development capabilities.