Service Layer
Introduction
The service layer provides a clean abstraction between API routes and database operations. Services encapsulate business logic, handle data validation, manage RLS context, and provide reusable methods for common operations.
Architecture
Service Layer Pattern
┌─────────────────────────────────────────┐
│ API Routes │
│ ├─ Request validation │
│ ├─ Authentication check │
│ └─ Call Service methods │
├─────────────────────────────────────────┤
│ Service Layer │
│ ├─ Business logic │
│ ├─ Data validation │
│ ├─ RLS context management │
│ ├─ Error handling │
│ └─ Return typed data │
├─────────────────────────────────────────┤
│ Database Layer (core/lib/db) │
│ ├─ queryWithRLS │
│ ├─ mutateWithRLS │
│ └─ Connection pooling │
└─────────────────────────────────────────┘
Benefits:
- Reusability: Share logic across routes and components
- Testability: Easy to unit test business logic
- Type Safety: TypeScript interfaces for data consistency
- Separation of Concerns: Clear boundaries between layers
- Centralized Validation: Consistent error handling
Core Services
User Service
Location: core/lib/services/user.service.ts
Purpose: User management, profile updates, metadata operations
Key Methods:
// Get user by ID or email
const user = await UserService.getUser(identifier, currentUserId);
// Get multiple users (avoids N+1)
const users = await UserService.getUsersByIds(userIds, currentUserId);
// Update user profile
const updated = await UserService.updateUser(
userId,
{ firstName: 'John', lastName: 'Doe' },
currentUserId
);
// User metadata operations
const metas = await UserService.getUserMetas(userId, currentUserId);
await UserService.updateUserMeta(userId, 'theme', 'dark', currentUserId);
Meta Service
Location: core/lib/services/meta.service.ts
Purpose: Flexible metadata storage for any entity
Key Methods:
// Get all metadata for an entity
const metas = await MetaService.getEntityMetas(
'user',
entityId,
currentUserId
);
// Set single metadata value
await MetaService.setEntityMeta(
'tasks',
taskId,
'priority',
'high',
currentUserId
);
// Bulk operations (prevents N+1)
const bulkMetas = await MetaService.getBulkEntityMetas(
'user',
userIds,
currentUserId
);
// Search by metadata
const results = await MetaService.searchByMeta(
'tasks',
'status',
'completed',
currentUserId
);
User Flags Service
Location: core/lib/services/user-flags.service.ts
Purpose: Feature flags and user permissions
Key Methods:
// Check if user has flag
const canAccess = await UserFlagsService.hasFlag(
userId,
'beta_features',
currentUserId
);
// Get all user flags
const flags = await UserFlagsService.getUserFlags(userId, currentUserId);
// Set flag value
await UserFlagsService.setFlag(
userId,
'notifications_enabled',
true,
currentUserId
);
Best Practices
Do's ✅
1. Always Pass userId for RLS
static async getTask(taskId: string, userId: string) {
return queryWithRLS('SELECT * FROM tasks WHERE id = $1', [taskId], userId);
}
2. Validate Input
if (!userId || userId.trim() === '') {
throw new Error('User ID is required');
}
3. Use TypeScript Types
static async getTask(id: string, userId: string): Promise<Task | null> {
// ...
}
4. Handle Errors Gracefully
try {
return await queryWithRLS(...);
} catch (error) {
console.error('Service error:', error);
throw new Error(
error instanceof Error ? error.message : 'Operation failed'
);
}
5. Use Bulk Operations
const entities = await queryWithRLS(
'SELECT * FROM entities WHERE id = ANY($1)',
[ids],
userId
);
Don'ts ❌
1. Never Bypass RLS
// ❌ BAD
const tasks = await query('SELECT * FROM tasks');
// ✅ GOOD
const tasks = await queryWithRLS('SELECT * FROM tasks', [], userId);
2. Never Query in Loops
// ❌ BAD
for (const id of ids) {
await queryWithRLS('SELECT * FROM tasks WHERE id = $1', [id], userId);
}
// ✅ GOOD
await queryWithRLS('SELECT * FROM tasks WHERE id = ANY($1)', [ids], userId);
Summary
Key Concepts:
- Services abstract business logic from API routes
- Always use RLS context (userId parameter)
- Type-safe interfaces for consistency
- Centralized error handling
- Reusable across application
See: User Service implementation
Next: Middleware
Last Updated: 2025-01-19 Version: 1.0.0 Status: Complete