LWS Job Portal is organized as a monorepo with the React frontend at the root and the Express backend nested inside aDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/iDevRanjan/lws-ra-b4-assignment-five/llms.txt
Use this file to discover all available pages before exploring further.
/backend subdirectory. This page documents every layer of that structure — from how React components are grouped by domain, through the service and routing layers, to the Express controllers and Sequelize models that power the API.
Monorepo Layout
The frontend and backend are completely decoupled — they communicate exclusively over HTTP through the REST API. Vite’s dev server proxies nothing; the frontend reads
VITE_API_BASE_URL from its environment file to locate the backend.Frontend Structure
Pages — src/pages/
Page-level components map one-to-one with application routes. They are organized by feature area:
| Subdirectory | Contents |
|---|---|
auth/ | Login, JobSeekerRegister, CompanyRegister |
public/ | Home, JobDetails, CompanyProfile |
jobSeeker/ | JobSeekerDashboard, JobSeekerApplications, JobSeekerProfile, EditJobSeekerProfile |
company/ | CompanyDashboard, ManageJobs, Applicants, ApplicantProfile, CreateAndEditJob, CompanySettings |
error/ | ErrorElement — rendered by React Router’s errorElement and the catch-all path: "*" route |
Components — src/components/
UI components follow a domain-driven folder structure:
jobs/
jobs/
Job-related display and interaction components:
JobCard, JobSearchAndFilter, ManageJobsTable, ApplyJobDialog, and the filter sidebar. These are used on both public and authenticated pages.company/
company/
Company-specific components:
CompanyInfo, ApplicantsCard, and CoverLetterModal for viewing an applicant’s cover letter inside a modal dialog.jobSeeker/
jobSeeker/
Profile and resume components scoped to the
USER role: JobSeekerExperience, JobSeekerSkills, and related profile section widgets.common/
common/
Generic, role-agnostic components reused across the app:
ApplyNowButton, Pagination, SearchInput, CompanyAvatar, and similar primitives.layout/
layout/
Shell components:
Header, Footer, RootFallback, and NavigationProgressBar. The Header renders a different navbar component (LoggedinJobSeekerNavbar from jobSeeker/ or LoggedinCompanyNavbar from company/) based on the authenticated user’s role, read from the custom store via useAuth. Note: MainLayout.jsx lives at src/layouts/MainLayout.jsx, not inside this subfolder.skeletons/
skeletons/
Placeholder shimmer components displayed during data fetches:
JobCardSkeleton, RecentJobsCardSkeleton, ApplicantsCardSkeleton. These replace spinners and prevent layout shift.Routing — src/routes/
React Router v7’s Data API drives all navigation. The routing layer has three files:
| File | Role |
|---|---|
AppRoutes.jsx | Creates the browser router with the full nested route tree and wraps it in RouterProvider |
RoleBasedRoute.jsx | An outlet wrapper that accepts an allowedRole prop ("USER" or "COMPANY") and redirects unauthorized users; paired with roleBasedLoader |
PublicRoutes.jsx | An outlet wrapper that redirects already-authenticated users away from login/register pages; paired with publicLoader |
routerLoaders.js) prefetch data before the page renders. If a loader throws, React Router activates the nearest errorElement rather than crashing the full layout.
Services & Networking — src/services/
All backend communication is centralized here:
| File | Purpose |
|---|---|
axiosInstance.js | A pre-configured Axios instance that attaches the JWT Authorization header to every request automatically |
authApi.js | login, register API calls |
jobApi.js | Job listing, detail, similar jobs, and recommendations |
companyApi.js | Company profile reads and updates |
userApi.js | Job seeker profile reads and updates |
queryOptions.js | Reusable TanStack Query queryOptions objects (e.g., getAllJobsQueryOption) for consistent cache key management |
mutationOptions.js | Reusable mutationOptions for create/update/delete operations |
routerLoaders.js | React Router loader functions that call API modules and return data to page components |
queryClient.js | Global QueryClient instance with default options shared across the app |
State Management — src/store/
LWS Job Portal does not use Redux or Zustand. Instead it implements a lightweight Pub-Sub store on top of React’s built-in useSyncExternalStore:
useAuth.js (in src/hooks/) consumes the store via useSyncExternalStore, giving any component reactive access to the current user’s login state and role without prop drilling or a context re-render cascade.
Hooks — src/hooks/
| Hook | Purpose |
|---|---|
useAuth.js | Exposes { isAuthenticated, user, role } from the custom Pub-Sub store |
useDebounce.js | Delays a value update by a configurable ms interval; used in the job search input to throttle API calls |
useQueryObject.js | Syncs filter state bidirectionally with URL query parameters |
useApplications.js | Encapsulates TanStack Query logic for fetching and mutating job applications |
useProfile.js | Encapsulates profile read and update query/mutation logic for job seekers |
Providers, Context, Data & Utils
| Directory | Contents |
|---|---|
src/providers/ | QueryObjectProvider.jsx — wraps the Home page to provide query-param filter state to the search and filter components |
src/context/ | Raw React context definitions consumed by providers |
src/data/ | Static arrays/objects for dropdowns and filters: categoryOptionData.js, statusFiltersData.js, skill lists, salary ranges |
src/utils/ | Pure helper functions: slugify.js, formatDate.js, getQueryParams.js, getDateDifferenceFromNow.js, applicationJobChecking.js |
Backend Structure
Entry Point — backend/server.js
server.js is the single Express application entry point. It:
- Loads environment variables via
dotenv - Registers global middleware: CORS (open origin), Morgan request logger, JSON and URL-encoded body parsers
- Serves uploaded files as static assets from
/uploads - Mounts all route modules under
/api/* - Registers a global error handler that formats errors as JSON
- Calls
sequelize.authenticate()andsequelize.sync()before binding to the port
Routes — backend/routes/
Each file defines the HTTP verbs and paths for one domain, then delegates to a controller:
| Route File | Mounted At | Domain |
|---|---|---|
authRoutes.js | /api/auth | Registration and login |
jobRoutes.js | /api/jobs | Job CRUD, search, pagination, recommendations |
userRoutes.js | /api/users | Job seeker profile management |
companyRoutes.js | /api/companies | Company profile management |
applicationRoutes.js | /api/applications | Apply, withdraw, status updates |
utilRoutes.js | /api/utils | Shared utility endpoints (e.g., option lists) |
Controllers — backend/controllers/
Controllers contain the business logic for each domain. They receive validated request data from the route layer, interact with Sequelize models, and return JSON responses. Errors are passed to next(err) and caught by the global error handler in server.js.
Models — backend/models/
Sequelize ORM models map to SQLite tables. The four core models are:
| Model | Key Fields |
|---|---|
User | id, name, email, password (bcrypt), role: "USER" |
Company | id, name, email, password (bcrypt), role: "COMPANY", description, socialLinks |
Job | id, slug, title, category, type, workMode, salary, skills, companyId |
Application | id, jobId, userId, coverLetter, status |
sequelize.sync({ force: false }) on server start creates tables if they do not exist without dropping existing data.
Middleware — backend/middleware/
| File | Exports | Purpose |
|---|---|---|
auth.js | protect, authorize | protect verifies the JWT from the Authorization header and attaches req.user. authorize(...roles) ensures the authenticated user’s role is in the permitted list. |
upload.js | Multer instance | Handles multipart/form-data for resume PDF uploads, saving files to uploads/resumes/ |
imageUpload.js | Multer instance | Handles image uploads (e.g., company logos) |
Database Config — backend/config/database.js
Configures and exports the Sequelize instance pointing to an SQLite file on disk. No external database server is required — SQLite is bundled via the sqlite3 npm package.
Data Flow
The following diagram describes how a request travels through every layer of the stack, from user interaction to database and back:Two-Role System
TheUSER / COMPANY role distinction is enforced at every layer:
- Frontend — RoleBasedRoute
RoleBasedRoute.jsx wraps protected child routes and receives an allowedRole prop. Its paired roleBasedLoader reads the current user from the store; if the role does not match allowedRole, the loader redirects to /login. This means a company user navigating to /jobseeker-dashboard is immediately redirected before any page component renders.