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
anytypes for API responses
Related Documentation
- ๐๏ธ ARCHITECTURE.md - Service architecture
- ๐ฃ HOOKS.md - Hook patterns
- ๐ API_INTEGRATION.md - API integration details
Last Updated: January 2026