Frontend
Services
API client layer and service modules for backend communication
Overview
Services handle all API communication with the backend. They are organized by feature and use a centralized API client.
API Client
The API client (@/services/api) provides a typed HTTP client with authentication support.
import { apiClient, ApiClientError } from "@/services/api";
// GET request
const response = await apiClient.get<User[]>("/users");
// POST request
const user = await apiClient.post<User>("/users", { name: "John" });
// With query parameters
const jobs = await apiClient.get<Job[]>("/jobs", { status: "active" });
// Set auth token
apiClient.setAuthToken(accessToken);Error Handling
import { ApiClientError } from "@/services/api";
try {
await apiClient.post("/login", credentials);
} catch (error) {
if (error instanceof ApiClientError) {
console.log(error.status); // HTTP status code
console.log(error.message); // Error message
console.log(error.data); // Response data
}
}API Endpoints
Endpoints are centralized in @/services/api/endpoints:
// services/api/endpoints.ts
export const AUTH = {
LOGIN: "/auth/login",
SIGNUP: "/auth/signup",
LOGOUT: "/auth/logout",
FORGOT_PASSWORD: "/auth/forgot-password",
RESET_PASSWORD: "/auth/reset-password",
REFRESH_TOKEN: "/auth/refresh",
VERIFY_EMAIL: "/auth/verify-email",
ME: "/auth/me",
};
export const WAITLIST = {
JOIN: "/waitlist/join",
TALENT: "/api/waitlist/talent",
EMPLOYER: "/api/waitlist/employer",
LIST: "/waitlist",
STATUS: "/waitlist/status",
};Auth Service
Handles authentication operations.
import { authService } from "@/features/auth";
// Login
const response = await authService.login({
email: "user@example.com",
password: "password123",
});
// Returns: { user, accessToken, refreshToken, expiresIn }
// Signup
const response = await authService.signup({
email: "user@example.com",
password: "password123",
firstName: "John",
lastName: "Doe",
role: "talent",
agreeToTerms: true,
});
// Logout
await authService.logout();
// Forgot password
await authService.forgotPassword({ email: "user@example.com" });
// Reset password
await authService.resetPassword({
token: "reset-token",
password: "newPassword123",
confirmPassword: "newPassword123",
});
// Refresh token
const { accessToken } = await authService.refreshToken({
refreshToken: "refresh-token",
});
// Get current user
const user = await authService.getCurrentUser();
// Verify email
await authService.verifyEmail("verification-token");Auth Service Implementation
// features/auth/services/auth.service.ts
import { apiClient } from "@/services/api";
import { AUTH } from "@/services/api/endpoints";
export const authService = {
async login(credentials: LoginRequest): Promise<LoginResponse> {
const response = await apiClient.post<LoginResponse>(
AUTH.LOGIN,
credentials
);
if (response.data.accessToken) {
apiClient.setAuthToken(response.data.accessToken);
}
return response.data;
},
async logout(): Promise<void> {
try {
await apiClient.post(AUTH.LOGOUT);
} finally {
apiClient.setAuthToken(null);
}
},
// ... other methods
};Waitlist Service
Handles waitlist operations for both talent and employers.
import { waitlistService } from "@/features/waitlist";
// Join talent waitlist
await waitlistService.joinTalent({
firstName: "John",
lastName: "Doe",
email: "john@example.com",
role: "Software Engineer",
agree: true,
});
// Join employer waitlist
await waitlistService.joinEmployer({
firstName: "Jane",
lastName: "Smith",
email: "jane@company.com",
company: "Tech Corp",
companySize: "51-200",
industry: "technology",
role: "HR Manager",
agree: true,
});
// Get waitlist entries (admin only)
const entries = await waitlistService.getEntries({
page: 1,
limit: 10,
sortBy: "createdAt",
sortOrder: "desc",
});
// Check waitlist status
const status = await waitlistService.checkStatus("user@example.com");
// Returns: { position: 42, total: 1000 }Service Pattern
When creating new services, follow this pattern:
// features/jobs/services/jobs.service.ts
import { apiClient } from "@/services/api";
import type { Job, CreateJobRequest } from "@hireable/shared/types/api";
export const jobsService = {
/**
* Get all jobs with optional filters
*/
async getAll(params?: { status?: string }): Promise<Job[]> {
const response = await apiClient.get<Job[]>("/jobs", params);
return response.data;
},
/**
* Get a single job by ID
*/
async getById(id: string): Promise<Job> {
const response = await apiClient.get<Job>(`/jobs/${id}`);
return response.data;
},
/**
* Create a new job posting
*/
async create(data: CreateJobRequest): Promise<Job> {
const response = await apiClient.post<Job>("/jobs", data);
return response.data;
},
/**
* Update an existing job
*/
async update(id: string, data: Partial<CreateJobRequest>): Promise<Job> {
const response = await apiClient.patch<Job>(`/jobs/${id}`, data);
return response.data;
},
/**
* Delete a job posting
*/
async delete(id: string): Promise<void> {
await apiClient.delete(`/jobs/${id}`);
},
};Backward Compatibility
The root services index re-exports from features for backward compatibility:
// services/index.ts
export * from "./api";
// Re-export from features for backward compatibility
export { authService } from "@/features/auth";
export { waitlistService } from "@/features/waitlist";New code should import directly from features:
// ✅ Preferred
import { authService } from "@/features/auth";
// ⚠️ Legacy (still works)
import { authService } from "@/services";Using Services with Hooks
Combine services with the useApi hook for state management:
import { useApi } from "@/hooks";
import { jobsService } from "@/features/jobs";
function JobsList() {
const { data: jobs, isLoading, error, execute } = useApi(
jobsService.getAll,
{ immediate: true }
);
const handleRefresh = () => execute();
if (isLoading) return <Spinner />;
if (error) return <Error message={error} />;
return (
<>
<Button onClick={handleRefresh}>Refresh</Button>
{jobs?.map(job => <JobCard key={job.id} job={job} />)}
</>
);
}