Hireable LogoHireable
Backend API

API Integration

How to integrate with the Hireable API from frontend applications

Overview

This guide covers how to integrate with the Hireable API from the frontend application using the provided services and hooks.

API Client Setup

The API client is configured in @/services/api:

// services/api/client.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api";
const API_TIMEOUT = Number(process.env.NEXT_PUBLIC_API_TIMEOUT) || 30000;

Making Requests

import { apiClient } from "@/services/api";
 
// GET request
const response = await apiClient.get<User[]>("/users");
 
// GET with query parameters
const jobs = await apiClient.get<Job[]>("/jobs", { 
  status: "active",
  page: 1 
});
 
// POST request
const user = await apiClient.post<User>("/users", { 
  name: "John",
  email: "john@example.com" 
});
 
// PATCH request
const updated = await apiClient.patch<User>("/users/123", { 
  name: "John Doe" 
});
 
// DELETE request
await apiClient.delete("/users/123");

Authentication

Set the auth token after login:

import { apiClient } from "@/services/api";
 
// After successful login
apiClient.setAuthToken(accessToken);
 
// Clear on logout
apiClient.setAuthToken(null);

The token is automatically included in the Authorization header:

Authorization: Bearer <token>

Using Services

Services encapsulate API calls for specific features:

// Import from features
import { authService } from "@/features/auth";
import { waitlistService } from "@/features/waitlist";
 
// Or from services (backward compatible)
import { authService, waitlistService } from "@/services";

Auth Service

import { authService } from "@/features/auth";
 
// Login
const { user, accessToken } = await authService.login({
  email: "user@example.com",
  password: "password123",
});
 
// Signup
const response = await authService.signup({
  email: "new@example.com",
  password: "password123",
  firstName: "John",
  lastName: "Doe",
  role: "talent",
  agreeToTerms: true,
});
 
// Logout
await authService.logout();
 
// Get current user
const user = await authService.getCurrentUser();

Waitlist Service

import { waitlistService } from "@/features/waitlist";
 
// Join as talent
await waitlistService.joinTalent({
  firstName: "John",
  lastName: "Doe",
  email: "john@example.com",
  role: "Developer",
  agree: true,
});
 
// Join as employer
await waitlistService.joinEmployer({
  firstName: "Jane",
  lastName: "Smith",
  email: "jane@company.com",
  company: "Tech Corp",
  companySize: "51-200",
  industry: "technology",
  role: "HR Manager",
  agree: true,
});

Using Hooks

useApi Hook

For generic API calls with state management:

import { useApi } from "@/hooks";
import { jobsService } from "@/features/jobs";
 
function JobsList() {
  const {
    data: jobs,
    isLoading,
    error,
    isSuccess,
    execute,
    reset,
  } = useApi(jobsService.getAll, {
    immediate: true,      // Fetch on mount
    cacheKey: "jobs",     // Cache results
    cacheDuration: 60000, // 1 minute
    retries: 2,           // Retry on failure
  });
 
  const handleRefresh = () => execute();
 
  if (isLoading) return <Spinner />;
  if (error) return <Error message={error} />;
 
  return (
    <div>
      <Button onClick={handleRefresh}>Refresh</Button>
      {jobs?.map(job => <JobCard key={job.id} job={job} />)}
    </div>
  );
}

useAuth Hook

For authentication state:

import { useAuth } from "@/hooks";
 
function LoginForm() {
  const { login, isLoading, error, clearError } = useAuth();
 
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await login({ email, password });
      router.push("/dashboard");
    } catch (err) {
      // Error is available in `error` state
    }
  };
 
  return (
    <form onSubmit={handleSubmit}>
      {error && <Alert>{error}</Alert>}
      <Input name="email" />
      <Input name="password" type="password" />
      <Button type="submit" disabled={isLoading}>
        {isLoading ? "Logging in..." : "Login"}
      </Button>
    </form>
  );
}

Error Handling

API Client Errors

import { ApiClientError } from "@/services/api";
 
try {
  await apiClient.post("/endpoint", data);
} 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
    
    if (error.status === 401) {
      // Handle unauthorized
    } else if (error.status === 400) {
      // Handle validation error
    }
  }
}

With useApi Hook

const { error, isError, execute } = useApi(apiFunction);
 
// Error is automatically captured
if (isError) {
  return <ErrorMessage>{error}</ErrorMessage>;
}

Type Safety

Import types from @hireable/shared:

import type {
  User,
  LoginRequest,
  LoginResponse,
  Job,
  Application,
} from "@hireable/shared/types/api";
 
// Use in components
interface Props {
  user: User;
  jobs: Job[];
}
 
// Use in services
async function login(credentials: LoginRequest): Promise<LoginResponse> {
  const response = await apiClient.post<LoginResponse>("/auth/login", credentials);
  return response.data;
}

Environment Configuration

Configure API settings in .env.local:

# API Base URL
NEXT_PUBLIC_API_URL=http://localhost:3001/api
 
# Request timeout (ms)
NEXT_PUBLIC_API_TIMEOUT=30000

Best Practices

  1. Use services for API calls - Don't call apiClient directly in components
  2. Use hooks for state management - useApi handles loading, error, and caching
  3. Import types from shared - Ensures type consistency across the stack
  4. Handle errors appropriately - Show user-friendly messages, log details
  5. Use caching wisely - Cache stable data, invalidate on mutations
// ✅ Good - Use service with hook
const { data, isLoading } = useApi(jobsService.getAll, { immediate: true });
 
// ❌ Avoid - Direct API call in component
const [data, setData] = useState();
useEffect(() => {
  apiClient.get("/jobs").then(res => setData(res.data));
}, []);

On this page