API INTEGRATION
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
API Integration Guide - User Management Module
Complete guide for integrating the user-management module with backend APIs.
Table of Contents
- API Service Overview
- API Endpoints
- Request/Response Models
- Authentication & Authorization
- Error Handling
- Mock API for Development
- Real API Integration
- Multi-Tenant Support
- Performance Optimization
API Service Overview
The UserApiService handles all HTTP communication with the backend:
import { UserApiService } from '@libs-core/user-management';
export class UserService {
private userApi = inject(UserApiService);
// All HTTP operations are handled through this service
}
Key Features:
- ✅ Automatic tenant context injection
- ✅ JWT token management
- ✅ Error handling and retry logic
- ✅ Type-safe request/response models
- ✅ RxJS observables for async operations
- ✅ HTTP interceptor integration
API Endpoints
Base URL Configuration
The service uses a configurable base URL:
// In environment files
export const environment = {
apiBaseUrl: 'https://api.example.com/v1',
userApiPath: '/users', // Default: '/users'
};
Endpoint Structure
BASE_URL/users
├── GET / - List users with filters
├── POST / - Create new user
├── GET /:id - Get user by ID
├── PUT /:id - Update user
├── DELETE /:id - Delete user
├── PATCH /:id/status - Change user status
└── POST /bulk - Bulk operations
Request/Response Models
List Users
Request:
GET /users?search=john&status=active&page=1&limit=20
Query Parameters:
interface UserListParams {
search?: string; // Search in name, email, username
status?: string[]; // User statuses (comma-separated)
roleIds?: string[]; // Role IDs (comma-separated)
createdAfter?: string; // ISO date string
createdBefore?: string; // ISO date string
lastActiveAfter?: string; // ISO date string
page?: number; // Page number (1-based)
limit?: number; // Items per page (default: 20)
sortBy?: string; // Field to sort by
sortOrder?: 'asc' | 'desc'; // Sort direction
}
Response:
interface UserListResponse {
users: User[];
total: number;
page: number;
limit: number;
totalPages: number;
}
Example:
const response = await firstValueFrom(
this.userApi.getUsers({
search: 'john',
status: ['active'],
page: 1,
limit: 20
})
);
console.log(response.users); // User[]
console.log(response.total); // 150
console.log(response.totalPages); // 8
Get User by ID
Request:
GET /users/:id
Response:
User | null
Example:
const user = await firstValueFrom(
this.userApi.getUserById('user-123')
);
if (user) {
console.log(user.email);
}
Create User
Request:
POST /users
Content-Type: application/json
{
"email": "[email protected]",
"username": "johndoe",
"firstName": "John",
"lastName": "Doe",
"primaryRole": "user",
"status": "active"
}
Request Model:
interface CreateUserRequest {
email: string; // Required, valid email
username: string; // Required, unique
firstName: string; // Required
lastName: string; // Required
primaryRole: SystemRoles; // Required
status?: UserStatus; // Optional, defaults to 'pending'
avatar?: string; // Optional, URL
metadata?: Record<string, unknown>; // Optional
}
Response:
User
Example:
const newUser = await firstValueFrom(
this.userApi.createUser({
email: '[email protected]',
username: 'johndoe',
firstName: 'John',
lastName: 'Doe',
primaryRole: SystemRoles.USER
})
);
console.log('Created user:', newUser.id);
Update User
Request:
PUT /users/:id
Content-Type: application/json
{
"firstName": "Jane",
"lastName": "Smith",
"avatar": "https://example.com/avatar.jpg"
}
Request Model:
interface UpdateUserRequest {
userId: string; // Required
email?: string; // Optional
username?: string; // Optional
firstName?: string; // Optional
lastName?: string; // Optional
avatar?: string; // Optional
metadata?: Record<string, unknown>; // Optional
}
Response:
User
Example:
const updated = await firstValueFrom(
this.userApi.updateUser({
userId: 'user-123',
firstName: 'Jane',
lastName: 'Smith'
})
);
Delete User
Request:
DELETE /users/:id
Response:
null
Example:
await firstValueFrom(
this.userApi.deleteUser('user-123')
);
console.log('User deleted successfully');
Change User Status
Request:
PATCH /users/:id/status
Content-Type: application/json
{
"newStatus": "suspended",
"reason": "Policy violation"
}
Request Model:
interface ChangeUserStatusRequest {
userId: string; // Required
newStatus: UserStatus; // Required: 'active' | 'inactive' | 'suspended' | 'pending'
reason?: string; // Optional
}
Response:
User
Example:
const updated = await firstValueFrom(
this.userApi.changeUserStatus({
userId: 'user-123',
newStatus: 'suspended',
reason: 'Policy violation'
})
);
Bulk Operations
Request:
POST /users/bulk
Content-Type: application/json
{
"operation": "activate",
"userIds": ["user-1", "user-2", "user-3"],
"options": {}
}
Request Model:
interface BulkOperationRequest {
operation: 'activate' | 'deactivate' | 'delete' | 'changeRole';
userIds: string[];
options: {
newRole?: string; // For 'changeRole' operation
reason?: string; // For status changes
force?: boolean; // Force delete even if user has data
};
}
Response:
interface BulkOperationResult {
successCount: number;
failureCount: number;
errors: Array<{
userId: string;
error: string;
}>;
}
Example:
const result = await firstValueFrom(
this.userApi.bulkUpdateUsers({
operation: 'activate',
userIds: ['user-1', 'user-2', 'user-3'],
options: {}
})
);
console.log(`Success: ${result.successCount}`);
console.log(`Failed: ${result.failureCount}`);
if (result.errors.length > 0) {
console.log('Errors:', result.errors);
}
Authentication & Authorization
JWT Token Management
The service automatically includes JWT tokens in requests:
// Automatic token injection via HTTP interceptor
GET /users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Token Refresh:
import { AuthService } from '@libs-core/security';
export class UserComponent {
private authService = inject(AuthService);
async loadUsers() {
// Token is automatically refreshed if expired
const users = await firstValueFrom(this.userApi.getUsers());
}
}
Permission Checks
Before making API calls, check permissions:
import { PermissionService } from '@libs-core/security';
export class UserActionsComponent {
private permissionService = inject(PermissionService);
private userApi = inject(UserApiService);
async createUser(request: CreateUserRequest) {
// Check permission before API call
if (!this.permissionService.hasPermission('users:create')) {
throw new Error('Insufficient permissions');
}
return firstValueFrom(this.userApi.createUser(request));
}
async deleteUser(userId: string) {
if (!this.permissionService.hasPermission('users:delete')) {
throw new Error('Insufficient permissions');
}
return firstValueFrom(this.userApi.deleteUser(userId));
}
}
Role-Based Access
import { RoleService, SystemRoles } from '@libs-core/security';
export class AdminActionsComponent {
private roleService = inject(RoleService);
private userApi = inject(UserApiService);
async performAdminAction() {
// Check role before API call
if (!this.roleService.hasRole(SystemRoles.ADMIN)) {
throw new Error('Admin role required');
}
// Perform admin-only operation
}
}
Error Handling
API Error Structure
interface ApiError {
status: number; // HTTP status code
statusText: string; // HTTP status text
error: {
code: string; // Error code (e.g., 'VALIDATION_ERROR')
message: string; // Human-readable message
details?: unknown; // Additional error details
timestamp: string; // ISO timestamp
path: string; // Request path
};
}
Handling Errors
import { catchError, throwError } from 'rxjs';
export class UserService {
private userApi = inject(UserApiService);
createUser(request: CreateUserRequest) {
return this.userApi.createUser(request).pipe(
catchError(error => {
// Log error
console.error('Create user failed:', error);
// Handle specific error codes
if (error.error?.code === 'DUPLICATE_EMAIL') {
this.showError('Email already exists');
} else if (error.error?.code === 'VALIDATION_ERROR') {
this.showError('Invalid user data');
} else {
this.showError('Failed to create user');
}
return throwError(() => error);
})
);
}
private showError(message: string) {
// Show error notification
}
}
Common Error Codes
| Code | Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Not authenticated |
FORBIDDEN |
403 | Insufficient permissions |
NOT_FOUND |
404 | User not found |
DUPLICATE_EMAIL |
409 | Email already exists |
DUPLICATE_USERNAME |
409 | Username already exists |
VALIDATION_ERROR |
400 | Invalid request data |
INTERNAL_ERROR |
500 | Server error |
Retry Logic
import { retry, delay } from 'rxjs/operators';
export class UserService {
loadUsers() {
return this.userApi.getUsers().pipe(
retry({
count: 3,
delay: 1000,
resetOnSuccess: true
}),
catchError(error => {
console.error('Failed after 3 retries:', error);
return throwError(() => error);
})
);
}
}
Mock API for Development
The module includes a complete mock API for development:
Enable Mock API
import { provideMockUserApi } from '@libs-core/user-management';
export const appConfig: ApplicationConfig = {
providers: [
provideMockUserApi(), // Use mock API instead of real HTTP
// ...
],
};
Mock Data
The mock API includes:
- 50 pre-generated users
- Realistic user data (names, emails, roles)
- Simulated network delays (200-500ms)
- Error simulation for testing
Mock API Features
// All operations work with mock data
const users = await firstValueFrom(this.userApi.getUsers());
// Returns mock users from in-memory storage
const user = await firstValueFrom(this.userApi.createUser(request));
// Adds to mock storage, generates ID
const updated = await firstValueFrom(this.userApi.updateUser(request));
// Updates mock storage
await firstValueFrom(this.userApi.deleteUser(id));
// Removes from mock storage
Customize Mock Data
import { USER_MOCK_DATA } from '@libs-core/user-management';
export const customMockData: User[] = [
{
id: 'custom-1',
email: '[email protected]',
username: 'admin',
// ... custom data
},
// Add more custom users
];
// Provide custom mock data
providers: [
{ provide: USER_MOCK_DATA, useValue: customMockData },
provideMockUserApi(),
]
Real API Integration
Configure API Endpoint
// environment.ts
export const environment = {
production: false,
apiBaseUrl: 'http://localhost:3000/api',
};
// environment.prod.ts
export const environment = {
production: true,
apiBaseUrl: 'https://api.example.com/v1',
};
Provide Real API Service
import { provideUserApi, USER_API_BASE_URL } from '@libs-core/user-management';
import { environment } from './environment';
export const appConfig: ApplicationConfig = {
providers: [
{ provide: USER_API_BASE_URL, useValue: environment.apiBaseUrl },
provideUserApi(), // Real HTTP implementation
// ...
],
};
HTTP Interceptors
The service integrates with Angular HTTP interceptors:
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor, tenantInterceptor } from '@libs-core/security';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
authInterceptor, // Adds JWT token
tenantInterceptor, // Adds tenant context
])
),
// ...
],
};
Custom Headers
Add custom headers to all requests:
import { HttpHeaders } from '@angular/common/http';
export class UserService {
private userApi = inject(UserApiService);
createUserWithCustomHeaders(request: CreateUserRequest) {
const headers = new HttpHeaders({
'X-Custom-Header': 'value',
'X-Request-ID': generateRequestId(),
});
// Note: UserApiService doesn't expose headers directly
// Use HttpClient wrapper if needed
}
}
Multi-Tenant Support
Automatic Tenant Context
The service automatically includes tenant context:
// Request headers automatically include:
X-Tenant-ID: tenant-123
// Injected from ContextService
Tenant-Specific Operations
import { ContextService } from '@libs-core/security';
export class UserService {
private contextService = inject(ContextService);
private userApi = inject(UserApiService);
async getUsersForCurrentTenant() {
const context = this.contextService.getCurrentContext();
console.log('Loading users for tenant:', context.tenantId);
// Tenant ID is automatically added to request
return firstValueFrom(this.userApi.getUsers());
}
async switchTenantAndLoadUsers(newTenantId: string) {
// Switch tenant context
this.contextService.setContext({ tenantId: newTenantId });
// Subsequent API calls use new tenant
return firstValueFrom(this.userApi.getUsers());
}
}
Tenant Isolation
All API operations are tenant-isolated:
- Users can only see users in their tenant
- Create/Update/Delete operations scoped to tenant
- Bulk operations limited to tenant users
- No cross-tenant data leakage
Performance Optimization
Caching
Implement caching for frequently accessed data:
import { shareReplay } from 'rxjs/operators';
export class UserService {
private userApi = inject(UserApiService);
private cache$ = new Map<string, Observable<User>>();
getUserById(id: string): Observable<User> {
if (!this.cache$.has(id)) {
this.cache$.set(
id,
this.userApi.getUserById(id).pipe(
shareReplay({ bufferSize: 1, refCount: true })
)
);
}
return this.cache$.get(id)!;
}
clearCache(id?: string) {
if (id) {
this.cache$.delete(id);
} else {
this.cache$.clear();
}
}
}
Pagination
Always use pagination for large datasets:
export class UserListComponent {
private userApi = inject(UserApiService);
currentPage = signal(1);
pageSize = signal(20);
users = toSignal(
toObservable(this.currentPage).pipe(
switchMap(page =>
this.userApi.getUsers({
page: page,
limit: this.pageSize()
})
)
),
{ initialValue: { users: [], total: 0 } }
);
nextPage() {
this.currentPage.update(p => p + 1);
}
previousPage() {
this.currentPage.update(p => Math.max(1, p - 1));
}
}
Request Debouncing
Debounce search requests:
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
export class UserSearchComponent {
private userApi = inject(UserApiService);
searchControl = new FormControl('');
searchResults = toSignal(
this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(query =>
this.userApi.getUsers({ search: query || undefined })
)
),
{ initialValue: { users: [], total: 0 } }
);
}
Request Cancellation
Cancel previous requests when new ones are made:
import { switchMap } from 'rxjs/operators';
export class UserListComponent {
private userApi = inject(UserApiService);
private filters$ = new BehaviorSubject<UserFilters>({});
// switchMap cancels previous request when new one is emitted
users$ = this.filters$.pipe(
switchMap(filters => this.userApi.getUsers(filters))
);
updateFilters(filters: UserFilters) {
this.filters$.next(filters);
}
}
Batch Operations
Batch multiple API calls:
import { forkJoin } from 'rxjs';
export class UserBatchService {
private userApi = inject(UserApiService);
loadMultipleUsers(userIds: string[]): Observable<User[]> {
const requests = userIds.map(id => this.userApi.getUserById(id));
return forkJoin(requests);
}
async batchUpdate(updates: UpdateUserRequest[]): Promise<User[]> {
// Use bulk API instead of individual calls
const bulkRequest = {
operation: 'update' as const,
updates: updates,
};
// If your API supports bulk updates
// return firstValueFrom(this.userApi.bulkUpdate(bulkRequest));
// Otherwise, sequential updates
const results: User[] = [];
for (const update of updates) {
const result = await firstValueFrom(this.userApi.updateUser(update));
results.push(result);
}
return results;
}
}
Testing API Integration
Unit Tests with Mock API
import { TestBed } from '@angular/core/testing';
import { UserApiService, provideMockUserApi } from '@libs-core/user-management';
describe('UserService', () => {
let service: UserApiService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideMockUserApi()]
});
service = TestBed.inject(UserApiService);
});
it('should load users', async () => {
const response = await firstValueFrom(service.getUsers());
expect(response.users).toBeDefined();
expect(response.users.length).toBeGreaterThan(0);
});
});
Integration Tests with HttpTestingController
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
describe('UserApiService', () => {
let service: UserApiService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(),
provideHttpClientTesting(),
UserApiService,
]
});
service = TestBed.inject(UserApiService);
httpMock = TestBed.inject(HttpTestingController);
});
it('should call correct endpoint', () => {
service.getUsers().subscribe();
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush({ users: [], total: 0 });
});
});
API Migration Checklist
When migrating from mock to real API:
- Configure
USER_API_BASE_URLin environment - Remove
provideMockUserApi()provider - Add
provideUserApi()provider - Configure HTTP interceptors (auth, tenant)
- Update error handling for real API error codes
- Test all CRUD operations
- Test bulk operations
- Test error scenarios
- Verify tenant isolation
- Test pagination
- Test search and filters
- Load test with realistic data volumes
- Monitor API performance
- Set up error logging and monitoring
API Documentation
For complete backend API documentation, refer to:
- OpenAPI/Swagger specification
- Backend API documentation
- Postman collection
- API versioning guide