Services Layer
Business logic and database operations made simple with CalmService.
What is a Service?#
A Service contains your business logic and database operations. It's the "chef" that:
- Performs database queries
- Implements business rules
- Processes data
- Returns results to controllers
The Power of CalmService#
CalmAPI provides CalmService - a base class with all CRUD operations!
Built-in Methods#
JavaScript
class CalmService {
async getAll(query, options) // Query with filters, pagination, sorting
async get(id, options) // Get single document by ID
async insert(data) // Create new document
async update(id, data) // Update document
async delete(id) // Delete document
}Basic Service Structure#
JavaScript
const { CalmService } = require('../../../system/core/CalmService');
class ProductService extends CalmService {
// Set fields to populate globally
populateFields = [
{ path: 'category' },
{ path: 'createdBy', select: 'name email' }
];
constructor(model) {
super(model); // Pass model to parent
}
// Add custom methods here
}
module.exports = { ProductService };Using Built-in Methods#
getAll() - Query Documents#
JavaScript
// Basic usage (in controller)
const response = await service.getAll(req.query);
// Returns: { data: [...], total: 100 }Query parameters:
JavaScript
// Pagination
?skip=0&limit=10
// Sorting
?sortBy[price]=1 // Ascending
?sortBy[price]=-1 // Descending
?sortBy[createdAt]=-1 // Newest first
// Filters
?status=active
?price[$gt]=100 // Greater than
?price[$lt]=1000 // Less than
?category=electronicsWith populate:
JavaScript
const response = await service.getAll(req.query, {
populateFields: [
{ path: 'category', select: 'name' },
{ path: 'createdBy', select: 'name email' }
]
});get() - Get Single Document#
JavaScript
const response = await service.get(productId);
// Returns: { data: {...} }With populate:
JavaScript
const response = await service.get(productId, {
populateFields: [
{ path: 'category' },
{ path: 'reviews', populate: { path: 'user' } }
]
});insert() - Create Document#
JavaScript
const data = {
name: 'iPhone 15',
price: 999,
category: categoryId
};
const response = await service.insert(data);
// Returns: { data: {...} }update() - Update Document#
JavaScript
const updates = {
price: 899,
stock: 50
};
const response = await service.update(productId, updates);
// Returns: { data: {...} }delete() - Delete Document#
JavaScript
const response = await service.delete(productId);
// Returns: { data: {...}, deleted: true }Global Populate#
Set populateFields to automatically load related data in getAll() and get():
JavaScript
class ProductService extends CalmService {
populateFields = [
{ path: 'category' },
{ path: 'createdBy', select: 'name email' },
{ path: 'updatedBy', select: 'name email' }
];
constructor(model) {
super(model);
}
}Now all queries will populate these fields automatically!
Custom Business Logic#
Add your own methods for specific operations:
JavaScript
class ProductService extends CalmService {
populateFields = [{ path: 'category' }];
constructor(model) {
super(model);
}
// Get featured products
async getFeatured(limit = 10) {
return await this.model
.find({ featured: true, status: 'active' })
.limit(limit)
.sort({ createdAt: -1 });
}
// Get products by category
async getByCategory(categoryId) {
return await this.model
.find({ category: categoryId, status: 'active' })
.populate('category');
}
// Search products
async search(searchTerm) {
return await this.model.find({
$text: { $search: searchTerm }
});
}
// Get low stock products
async getLowStock() {
return await this.model.find({
$expr: { $lte: ['$stock', '$lowStockThreshold'] }
});
}
// Update stock
async updateStock(productId, quantity) {
const product = await this.model.findById(productId);
if (!product) {
throw new Error('Product not found');
}
if (product.stock < quantity) {
throw new Error('Insufficient stock');
}
product.stock -= quantity;
await product.save();
return product;
}
}Tips & Best Practices#
1. Use Services for All Database Operations#
Don't query the database directly in controllers!
JavaScript
// Good ✅
const products = await this.service.getFeatured();
// Bad ❌
const products = await Product.find({ featured: true });2. Keep Business Logic in Services#
JavaScript
// Calculate order total in service
async calculateTotal(items) {
return items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
}3. Throw Descriptive Errors#
JavaScript
if (!product) {
const error = new Error('Product not found');
error.statusCode = 404;
throw error;
}