ApplyBoard is a full-stack job application tracker that helps users organize job opportunities, applications, interview stages, notes, and follow-ups in one place.
The app includes custom authentication using JWTs stored in HTTP-only cookies, a React/TypeScript frontend, an Express/TypeScript backend, and Firestore for persistent data storage.
- User registration and login
- JWT authentication with HTTP-only cookies
- Protected backend routes
- Auth state management with React Context
- Create job applications
- View applications for the logged-in user
- Update existing applications
- Delete applications
- Store application data in Firestore
- Responsive login and signup pages
- React
- TypeScript
- React Router
- Context API
- CSS
- Node.js
- Express
- TypeScript
- JWT
- bcrypt
- cookie-parser
- CORS
- Firebase Admin SDK
- Firebase Firestore
project-root/
client/
src/
pages/
Login.tsx
Signup.tsx
Home.tsx
AuthProvider.tsx
App.tsx
server/
src/
server.ts
firebase.ts
controller/
user.ts
application.ts
middleware/
authenticateToken.ts
getJWTSecret.ts
types/
types.ts
express.d.ts
serviceAccountKey.json
.env
package.json
tsconfig.jsonThis project uses JWT authentication with HTTP-only cookies.
When a user logs in:
User submits email/password
↓
Express verifies credentials
↓
Express creates a JWT
↓
JWT is sent to the browser as an HTTP-only cookie
↓
Browser automatically includes the cookie on future requests
↓
Protected routes verify the JWT before allowing accessThe frontend does not directly read the JWT. Instead, it checks whether the user is authenticated by calling the backend /me route.
Create a .env file inside the server/ folder:
PORT=3000
JWT_SECRET=your_long_random_secret_hereThis project uses Firestore through the Firebase Admin SDK.
- Create a Firebase project.
- Enable Firestore.
- Go to Project Settings → Service accounts → Generate new private key.
- Download the service account JSON file.
- Place it inside the
server/folder asserviceAccountKey.json. - Add the file to
.gitignore.
.env
serviceAccountKey.jsonDo not commit your service account key to GitHub.
From the server/ folder:
npm installStart the backend:
npm run devThe backend runs on:
http://localhost:3000From the client/ folder:
npm installStart the frontend:
npm run devThe frontend runs on:
http://localhost:5173POST /users/register
POST /users/login
GET /me
POST /logoutGET /applications
POST /applications
PUT /applications/:id
DELETE /applications/:idPOST /users/registerExample body:
{
"email": "user@example.com",
"passwordOne": "password123",
"passwordTwo": "password123"
}POST /users/loginExample body:
{
"email": "user@example.com",
"password": "password123"
}On successful login, the server sets an HTTP-only cookie containing the JWT.
GET /meThis route checks the JWT cookie and returns the authenticated user.
POST /logoutThis clears the JWT cookie.
All application routes require authentication.
GET /applicationsReturns all applications owned by the logged-in user.
POST /applicationsExample body:
{
"company": "AcuityMD",
"role": "Software Engineer",
"status": "Applied",
"jobUrl": "https://example.com/job",
"location": "Remote",
"salaryRange": "$100k - $130k",
"notes": "Frontend/full-stack role"
}The backend automatically attaches the logged-in user’s ID to the application.
PUT /applications/:idUpdates a specific application if it belongs to the logged-in user.
DELETE /applications/:idDeletes a specific application if it belongs to the logged-in user.
{
email: string;
password: string; // hashed password
createdAt: Date;
}{
userId: string;
company: string;
role: string;
status: string;
jobUrl: string;
location: string;
salaryRange: string;
notes: string;
createdAt: Date;
updatedAt: Date;
}The frontend uses an AuthProvider to store the current user and authentication loading state.
The app checks authentication by calling:
GET /meIf the cookie is valid, the backend returns the user and the context stores it.
This allows components to access auth state with:
const { user, loading, checkAuth, logout } = useAuth();- Passwords are hashed with bcrypt before being stored.
- JWTs are stored in HTTP-only cookies.
- Protected routes verify the JWT before accessing user data.
- Application routes only return, update, or delete documents owned by the authenticated user.
- Firestore access is handled through the backend using the Firebase Admin SDK.
- The service account key should never be committed to version control.
- Add application status filters
- Add search by company or role
- Add interview dates and reminders
- Add notes history per application
- Add dashboard analytics
- Add AI-generated interview prep from job descriptions
- Add refresh tokens or longer-lived session handling
- Add form validation and user-facing error messages
- Add unit and integration tests
- Add deployment instructions
This project is currently in development. The core authentication and Firestore-backed application CRUD flow are being implemented.