Skip to Content

Services Documentation

Overview

The services directory (src/services/) contains all API communication logic. It abstracts HTTP requests, handles authentication, manages request/response transformations, and provides type-safe API interactions.

Directory Structure

src/services/ └── api/ ├── HR/ # HR-related endpoints (11) ├── audit-trail/ # Audit logging (2) ├── auth.ts # Authentication service ├── beneficiary-enrollment/ # Enrollment workflows (15) ├── beneficiary-portal/ # Portal APIs (2) ├── beneficiary-provider/ # Provider APIs (1) ├── client-company/ # Company management (45) ├── cocolife/ # Cocolife provider (19) ├── etiqa/ # Etiqa provider (17) ├── forgot-password/ # Password recovery (3) ├── generali/ # Generali provider (21) ├── icare/ # Icare provider (6) ├── insurance-company-values/ # Company values (18) ├── insurance-plan-schema.ts # Schema definitions ├── insurance-provider/ # Provider metadata (1) ├── intellicare/ # Intellicare provider (23) ├── logged-in-users/ # Active sessions (1) ├── maxicare/ # Maxicare provider (17) ├── principal/ # Principal portal (16) ├── reset-password/ # Password reset (3) ├── user/ # User management (2) ├── instance.ts # Axios instances ├── interface.ts # Shared interfaces └── response.ts # Response types

API Architecture

Instance Configuration (instance.ts)

Two Axios instances handle different authentication requirements:

Public API Instance

For unauthenticated endpoints:

export const publicApi = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_ENDPOINT + '/api/v1/public', timeout: 10000, headers: { 'Content-Type': 'application/json', }, });

Used for:

  • Authentication (login, OTP)
  • Password reset
  • Public data retrieval

Private API Instance

For authenticated endpoints with dynamic token injection:

export function privateApi(credentials: Credentials) { return axios.create({ baseURL: process.env.NEXT_PUBLIC_API_ENDPOINT + '/api/v1/private', timeout: 30000, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${credentials.accessToken}`, }, }); }

Features:

  • Bearer token authentication
  • Automatic token refresh on 401
  • Request/response interceptors
  • Audit logging integration

Interceptors

Request interceptor adds audit context:

privateApi.interceptors.request.use((config) => { // Add audit tracking headers config.headers['X-User-ID'] = credentials.userId; config.headers['X-Action'] = config.metadata?.action; return config; });

Response interceptor handles token refresh:

privateApi.interceptors.response.use( (response) => response, async (error) => { if (error.response?.status === 401) { // Attempt token refresh const newToken = await refreshToken(); // Retry request with new token } return Promise.reject(error); } );

Service Patterns

Standard Service Structure

Each service module follows this pattern:

// services/api/my-feature/index.ts import { z } from 'zod'; import { privateApi } from '../instance'; // 1. Define schemas export const MyDataSchema = z.object({ id: z.string().uuid(), name: z.string(), status: z.enum(['active', 'inactive']), }); export type MyData = z.infer<typeof MyDataSchema>; // 2. Raw API functions export async function getMyData(credentials: Credentials, params: QueryParams) { const api = privateApi(credentials); const response = await api.get('/my-endpoint', { params }); return MyDataSchema.array().parse(response.data); } export async function createMyData(credentials: Credentials, data: CreateMyDataInput) { const api = privateApi(credentials); const response = await api.post('/my-endpoint', data); return MyDataSchema.parse(response.data); } export async function updateMyData( credentials: Credentials, id: string, data: UpdateMyDataInput ) { const api = privateApi(credentials); const response = await api.put(`/my-endpoint/${id}`, data); return MyDataSchema.parse(response.data); } export async function deleteMyData(credentials: Credentials, id: string) { const api = privateApi(credentials); await api.delete(`/my-endpoint/${id}`); } // 3. React Query hooks (optional, can be in separate hooks file) export function useMyData(params: QueryParams) { const { data: session } = useSession(); return useQuery({ queryKey: ['my-data', params], queryFn: () => getMyData(session!.user, params), enabled: !!session, }); }

Core Services

Authentication (auth.ts)

Handles all authentication operations.

FunctionMethodEndpointPurpose
login()POST/public/auth/loginInitial login
requestOTP()POST/public/auth/otpRequest OTP code
verifyOTP()POST/public/auth/verifyVerify OTP
refreshToken()POST/private/auth/refreshRefresh access token
getSession()GET/private/auth/sessionGet current session
logout()POST/private/auth/logoutEnd session

Example:

import { login, requestOTP, verifyOTP } from '@/services/api/auth'; // Login flow const { tempToken } = await login({ email, password }); await requestOTP({ tempToken }); const { accessToken, refreshToken } = await verifyOTP({ tempToken, otp });

Client Company (client-company/)

Company management operations (45 API functions).

Categories:

  • Company CRUD operations
  • Company-Provider associations
  • Employee management
  • Plan assignments

Example:

import { getClientCompanies, createClientCompany, updateClientCompany, deleteClientCompany, } from '@/services/api/client-company'; const companies = await getClientCompanies(credentials, { page: 1, limit: 25 });

Insurance Providers

Each provider has dedicated API modules:

ProviderDirectoryFunctions
Cocolifecocolife/19
Etiqaetiqa/17
Generaligenerali/21
Icareicare/6
Intellicareintellicare/23
Maxicaremaxicare/17

Provider API Pattern:

// services/api/cocolife/index.ts export async function getCocolifeMembers(credentials: Credentials, params: MemberParams) { const api = privateApi(credentials); const response = await api.get('/cocolife/members', { params }); return CocolifeMemberSchema.array().parse(response.data); } export async function getCocolifePlans(credentials: Credentials) { const api = privateApi(credentials); const response = await api.get('/cocolife/plans'); return CocolifePlanSchema.array().parse(response.data); }

Beneficiary Enrollment (beneficiary-enrollment/)

Enrollment workflow APIs (15 functions).

Operations:

  • Submit enrollment
  • Get enrollment status
  • Update enrollment
  • Cancel enrollment
  • Upload documents
import { submitEnrollment, getEnrollmentStatus, uploadEnrollmentDocument, } from '@/services/api/beneficiary-enrollment';

HR (HR/)

HR portal operations (11 functions).

Operations:

  • Employee management
  • Bulk operations
  • Reports generation
  • Approvals workflow

Audit Trail (audit-trail/)

Audit logging APIs (2 functions).

import { getAuditLogs, getLoggedInUsers } from '@/services/api/audit-trail'; const logs = await getAuditLogs(credentials, { startDate, endDate, action }); const activeUsers = await getLoggedInUsers(credentials);

Response Types (response.ts)

Standardized API response types:

// Common response wrapper export interface ApiResponse<T> { data: T; message: string; status: number; } // Paginated response export interface PaginatedResponse<T> { data: T[]; pagination: { page: number; limit: number; total: number; totalPages: number; }; } // Error response export interface ApiError { message: string; code: string; errors?: Record<string, string[]>; }

Interfaces (interface.ts)

Shared API interfaces:

export interface Credentials { accessToken: string; refreshToken: string; userId: string; userType: 'Broker' | 'Principal' | 'Beneficiary'; } export interface QueryParams { page?: number; limit?: number; sort?: string; order?: 'asc' | 'desc'; search?: string; filters?: Record<string, unknown>; } export interface ApiConfig { timeout?: number; retries?: number; headers?: Record<string, string>; }

Error Handling

Service-Level Error Handling

import { handleApiError } from '@/utils/error-handler'; export async function getData(credentials: Credentials) { try { const api = privateApi(credentials); const response = await api.get('/data'); return schema.parse(response.data); } catch (error) { if (axios.isAxiosError(error)) { // Handle specific HTTP errors if (error.response?.status === 404) { throw new NotFoundError('Data not found'); } if (error.response?.status === 403) { throw new ForbiddenError('Access denied'); } } throw error; } }

Error Handler Utility

// utils/error-handler.ts export function handleApiError(error: unknown): ApiError { if (axios.isAxiosError(error)) { return { message: error.response?.data?.message || error.message, code: error.code || 'UNKNOWN_ERROR', status: error.response?.status, }; } return { message: 'An unexpected error occurred', code: 'UNKNOWN_ERROR', }; }

Creating New Services

1. Create Directory

services/api/my-feature/ ├── index.ts # Main exports ├── schemas.ts # Zod schemas (optional) └── types.ts # TypeScript types (optional)

2. Define Schema

// services/api/my-feature/schemas.ts import { z } from 'zod'; export const MyFeatureSchema = z.object({ id: z.string().uuid(), name: z.string().min(1).max(100), createdAt: z.string().datetime(), isActive: z.boolean().default(true), }); export type MyFeature = z.infer<typeof MyFeatureSchema>;

3. Implement API Functions

// services/api/my-feature/index.ts import { privateApi } from '../instance'; import { MyFeatureSchema } from './schemas'; import type { MyFeature } from './schemas'; export async function getMyFeatures(credentials: Credentials) { const api = privateApi(credentials); const response = await api.get('/my-feature'); return MyFeatureSchema.array().parse(response.data); } export async function createMyFeature( credentials: Credentials, data: Omit<MyFeature, 'id' | 'createdAt'> ) { const api = privateApi(credentials); const response = await api.post('/my-feature', data); return MyFeatureSchema.parse(response.data); }

4. Create React Query Hook

// lib/hooks/my-feature.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { getMyFeatures, createMyFeature } from '@/services/api/my-feature'; import { useSession } from 'next-auth/react'; export function useMyFeatures() { const { data: session } = useSession(); return useQuery({ queryKey: ['my-features'], queryFn: () => getMyFeatures(session!.user), enabled: !!session, }); } export function useCreateMyFeature() { const { data: session } = useSession(); const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateInput) => createMyFeature(session!.user, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['my-features'] }); }, }); }

Best Practices

  1. Always validate responses with Zod schemas
  2. Type all inputs and outputs explicitly
  3. Handle errors gracefully with specific error types
  4. Use React Query for client-side data fetching
  5. Invalidate queries after mutations
  6. Add audit context to requests
  7. Cache appropriately using React Query cache config
  8. Log API calls for debugging (dev only)

Last updated on