Hireable LogoHireable
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} />)}
    </>
  );
}

On this page