Skip to content

Services Reference

Complete guide to API services and backend communication in Constellation.

Services Overview

Services handle all backend API communication and are located in src/services/.

src/services/
โ”œโ”€โ”€ apiClient.ts          # Core HTTP client
โ”œโ”€โ”€ token.ts              # Token management
โ”œโ”€โ”€ userService.ts        # User operations
โ”œโ”€โ”€ workspaceService.ts   # Workspace operations
โ””โ”€โ”€ admin/
    โ””โ”€โ”€ adminService.ts   # Admin operations

API Client

Overview

The core HTTP client for all API communication.

Location: src/services/apiClient.ts or src/lib/api.ts

Basic Usage

import { apiClient } from '@/src/lib/api';

// GET request
const user = await apiClient.get('/users/123');

// POST request
const newWorkspace = await apiClient.post('/workspaces', {
  name: 'My Workspace',
});

// PUT request
await apiClient.put('/workspaces/123', { name: 'Updated' });

// DELETE request
await apiClient.delete('/workspaces/123');

Configuration

const apiClient = new APIClient({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  timeout: 30000,
  retries: 3,
  headers: {
    'Content-Type': 'application/json',
  },
});

Error Handling

try {
  const data = await apiClient.get('/users/123');
} catch (error) {
  if (error instanceof APIError) {
    console.error(`API Error: ${error.status} - ${error.message}`);

    if (error.status === 404) {
      // Handle not found
    } else if (error.status === 401) {
      // Handle unauthorized - redirect to login
    } else if (error.status === 500) {
      // Handle server error
    }
  }
}

Request Interceptors

Add custom headers or authentication:

apiClient.addRequestInterceptor((config) => {
  const token = getAuthToken();
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

Response Interceptors

Handle responses globally:

apiClient.addResponseInterceptor((response) => {
  // Transform response
  return response.data;
}, (error) => {
  // Handle errors globally
  if (error.status === 401) {
    // Refresh token or redirect
  }
  throw error;
});

Token Management

Token Service

Location: src/services/token.ts

import { tokenService } from '@/src/services/token';

// Store token
tokenService.setToken(token);

// Get token
const token = tokenService.getToken();

// Refresh token
const newToken = await tokenService.refreshToken();

// Clear token
tokenService.clearToken();

// Check if token exists
if (tokenService.hasToken()) {
  // Token exists
}

Token Validation

import { isTokenValid, getTokenExpiry } from '@/src/services/token';

if (isTokenValid()) {
  // Token is valid
}

const expiryTime = getTokenExpiry(); // Returns Date or null

Automatic Token Refresh

// Automatically refresh token before expiry
tokenService.setAutoRefresh(true, 300000); // Refresh 5 min before expiry

User Service

Get User Profile

import { userService } from '@/src/services/userService';

const user = await userService.getProfile();
// Returns: User

const userById = await userService.getUserById('user-123');
// Returns: User

Update User

const updated = await userService.updateProfile({
  name: 'New Name',
  email: 'new@example.com',
});
// Returns: User

List Users (Admin)

const users = await userService.listUsers({
  page: 1,
  limit: 20,
  search: 'query',
  role: 'admin',
});
// Returns: { data: User[], total: number, page: number }

User Preferences

// Get preferences
const prefs = await userService.getPreferences();

// Update preferences
await userService.updatePreferences({
  language: 'fr',
  theme: 'dark',
  notifications: true,
});

Workspace Service

Create Workspace

import { workspaceService } from '@/src/services/workspaceService';

const workspace = await workspaceService.create({
  name: 'My Workspace',
  description: 'Workspace description',
});
// Returns: Workspace

Get Workspace

const workspace = await workspaceService.getById('workspace-123');
// Returns: Workspace

const workspaces = await workspaceService.list({
  page: 1,
  limit: 20,
});
// Returns: { data: Workspace[], total: number }

Update Workspace

const updated = await workspaceService.update('workspace-123', {
  name: 'Updated Name',
  description: 'New description',
});
// Returns: Workspace

Delete Workspace

await workspaceService.delete('workspace-123');

Workspace Members

// Add member
await workspaceService.addMember('workspace-123', {
  userId: 'user-456',
  role: 'editor', // 'owner' | 'editor' | 'viewer'
});

// Remove member
await workspaceService.removeMember('workspace-123', 'user-456');

// Update member role
await workspaceService.updateMemberRole('workspace-123', 'user-456', 'admin');

// List members
const members = await workspaceService.listMembers('workspace-123');
// Returns: WorkspaceMember[]

Workspace Content

// Get content
const content = await workspaceService.getContent('workspace-123');
// Returns: WorkspaceContent

// Update content
const updated = await workspaceService.updateContent('workspace-123', {
  data: contentData,
  version: 1,
});
// Returns: WorkspaceContent

Real-time Services

Server-Sent Events (SSE)

import { sseClient } from '@/src/services/sseClient';

// Subscribe to events
const unsubscribe = sseClient.subscribe('/events', (event) => {
  console.log('Event received:', event);
});

// Cleanup
unsubscribe();

WebSocket (Hocuspocus)

import { hocuspocusProvider } from '@/src/services/hocuspocusProvider';

// Connect to document
const provider = hocuspocusProvider.connect('document-123', {
  awareness: true,
  onConnect: () => console.log('Connected'),
  onDisconnect: () => console.log('Disconnected'),
  onUpdate: (data) => console.log('Update:', data),
});

// Disconnect
provider.disconnect();

Admin Service

User Management

import { adminService } from '@/src/services/admin/adminService';

// List all users
const users = await adminService.listUsers({
  page: 1,
  limit: 50,
  role: 'user',
});

// Delete user
await adminService.deleteUser('user-123');

// Update user role
await adminService.updateUserRole('user-123', 'admin');

// Ban user
await adminService.banUser('user-123', 'Spam');

// Unban user
await adminService.unbanUser('user-123');

Content Moderation

// List reported content
const reports = await adminService.listReports({
  status: 'pending',
  page: 1,
});

// Approve content
await adminService.approveContent('report-123');

// Reject content
await adminService.rejectContent('report-123', 'Policy violation');

// Delete content
await adminService.deleteContent('content-123');

System Stats

const stats = await adminService.getSystemStats();
// Returns: {
//   totalUsers: number,
//   activeUsers: number,
//   totalWorkspaces: number,
//   apiRequests: number,
// }

Creating New Services

Service Template

// src/services/myService.ts
import { apiClient } from '@/src/lib/api';
import type { MyResource } from '@/src/types/my-resource';

class MyService {
  /**
   * Get a resource by ID
   * @param id - Resource ID
   * @returns Resource data
   */
  async getById(id: string): Promise<MyResource> {
    return apiClient.get(`/my-resource/${id}`);
  }

  /**
   * List resources
   * @param options - Query options
   * @returns List of resources
   */
  async list(options: ListOptions): Promise<PaginatedResponse<MyResource>> {
    return apiClient.get('/my-resource', { params: options });
  }

  /**
   * Create a resource
   * @param data - Resource data
   * @returns Created resource
   */
  async create(data: CreateMyResourceDTO): Promise<MyResource> {
    return apiClient.post('/my-resource', data);
  }

  /**
   * Update a resource
   * @param id - Resource ID
   * @param data - Updated data
   * @returns Updated resource
   */
  async update(
    id: string,
    data: UpdateMyResourceDTO
  ): Promise<MyResource> {
    return apiClient.put(`/my-resource/${id}`, data);
  }

  /**
   * Delete a resource
   * @param id - Resource ID
   */
  async delete(id: string): Promise<void> {
    await apiClient.delete(`/my-resource/${id}`);
  }
}

export const myService = new MyService();

Usage in Hooks

// src/hooks/use-my-resource.ts
import { useEffect, useState } from 'react';
import { myService } from '@/src/services/myService';
import type { MyResource } from '@/src/types/my-resource';

export function useMyResource(id: string) {
  const [resource, setResource] = useState<MyResource | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    (async () => {
      try {
        const data = await myService.getById(id);
        setResource(data);
      } catch (err) {
        setError(err instanceof Error ? err : new Error('Unknown error'));
      } finally {
        setLoading(false);
      }
    })();
  }, [id]);

  return { resource, loading, error };
}

Usage in Components

// src/components/MyResourceViewer.tsx
'use client';

import { useMyResource } from '@/src/hooks/use-my-resource';

export function MyResourceViewer({ resourceId }: Props) {
  const { resource, loading, error } = useMyResource(resourceId);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!resource) return null;

  return <div>{resource.name}</div>;
}

Error Handling Strategies

API Error Class

class APIError extends Error {
  constructor(
    public status: number,
    public message: string,
    public data?: any
  ) {
    super(message);
  }
}

Try-Catch Pattern

try {
  const data = await apiClient.get('/endpoint');
} catch (error) {
  if (error instanceof APIError) {
    if (error.status === 401) {
      // Unauthorized - redirect to login
      window.location.href = '/login';
    } else if (error.status === 403) {
      // Forbidden - show permission error
    } else {
      // Other error
    }
  }
}

Retries

async function fetchWithRetry(fn: () => Promise<T>, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      // Wait before retry (exponential backoff)
      await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
    }
  }
}

Best Practices

โœ… Do's

  • โœ… Use services for all API calls
  • โœ… Handle errors explicitly
  • โœ… Add loading/error states
  • โœ… Type API responses
  • โœ… Use request interceptors for auth
  • โœ… Document API contracts
  • โœ… Implement retry logic
  • โœ… Cache responses when appropriate

โŒ Don'ts

  • โŒ Make direct fetch calls in components
  • โŒ Ignore error responses
  • โŒ Hardcode API URLs
  • โŒ Skip type checking
  • โŒ Make API calls in render
  • โŒ Forget to cleanup subscriptions
  • โŒ Expose sensitive data in logs
  • โŒ Use any types for API responses


Last Updated: January 2026