Service Layer Usage Examples
Basic Controller Using CrudService
All controllers extending BaseController automatically get access to CRUD operations through the service layer:
import { IGallery } from "../interfaces/gallery.interface";
import Gallery from "../models/gallery.model";
import { BaseController } from "./base.controller";
class GalleryController extends BaseController<IGallery> {
constructor() {
super(Gallery);
// this.crudService is automatically available
}
/**
* Generate SEO-optimized slug for galleries
*/
protected generateSlug(item: IGallery): string | undefined {
const year = item.createdAt
? new Date(item.createdAt).getFullYear()
: new Date().getFullYear();
return this.SlugGenerator.generateGallerySlug({
title: item.title,
year: year,
});
}
}
Using Services Directly in Custom Methods
import { Request, Response, NextFunction } from "express";
import { BaseController } from "./base.controller";
import { loggingService } from "../services/logging.service";
import { fileManagementService } from "../services/fileManagement.service";
class CustomController extends BaseController<ICustom> {
// Custom method using services directly
async customOperation(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
// Use CRUD service
const items = await this.crudService.getAll({
module_id: req.body.module_id,
page: 1,
limit: 10,
});
// Use logging service
await loggingService.logAction(
"Custom operation performed",
200,
req.user?._id,
req.ip,
req.get("User-Agent")
);
res.status(200).json(items);
} catch (error) {
next(error);
}
}
// Custom file handling
async customFileOperation(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { fileId } = req.params;
// Regenerate thumbnail with custom size
const result = await fileManagementService.regenerateThumbnail(
fileId,
{ width: 500, height: 500 }
);
res.status(200).json(result);
} catch (error) {
next(error);
}
}
}
Using Module Service for Complex Operations
import { Request, Response, NextFunction } from "express";
import { moduleService } from "../services/module.service";
import errorService from "../utils/error.util";
class AdminController {
async bulkModuleOperation(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const { moduleIds } = req.body;
// Delete multiple modules with all their content
const result = await moduleService.deleteModules(
moduleIds,
req.user?._id,
req.ip,
req.get("User-Agent")
);
res.status(200).json({
message: "Modules deleted successfully",
...result,
});
} catch (error) {
next(errorService.internal("Error deleting modules", error));
}
}
async regenerateAllThumbnails(
req: Request,
res: Response,
next: NextFunction
): Promise<void> {
try {
const modules = await moduleService.getAllModules();
const results = [];
for (const module of modules) {
try {
const result = await moduleService.regenerateThumbnails(
module._id.toString(),
req.user?._id,
req.ip,
req.get("User-Agent")
);
results.push({ moduleId: module._id, ...result });
} catch (error) {
results.push({
moduleId: module._id,
error: error.message,
});
}
}
res.status(200).json({
message: "Thumbnail regeneration complete",
results,
});
} catch (error) {
next(errorService.internal("Error regenerating thumbnails", error));
}
}
}
Creating Custom Services
// src/services/myCustom.service.ts
import { loggingService } from "./logging.service";
import { fileManagementService } from "./fileManagement.service";
import MyModel from "../models/myModel.model";
export class MyCustomService {
async customBusinessLogic(data: any): Promise<any> {
// Implement your business logic
const result = await MyModel.find(data);
// Use other services
await loggingService.logAction(
"Custom business logic executed",
200
);
return result;
}
async processFiles(files: string[]): Promise<void> {
// Use file management service
for (const fileId of files) {
await fileManagementService.regenerateThumbnail(fileId, {
width: 300,
height: 300,
});
}
}
}
export const myCustomService = new MyCustomService();
Service Composition
// Compose multiple services for complex operations
import { CrudService } from "../services/crud.service";
import { loggingService } from "../services/logging.service";
import { fileManagementService } from "../services/fileManagement.service";
import { tableOrderService } from "../services/tableOrder.service";
class ComplexOperationService {
private crudService: CrudService<any>;
constructor(model: Model<any>) {
this.crudService = new CrudService(model);
}
async performComplexOperation(data: any): Promise<any> {
// Step 1: Create item
const item = await this.crudService.create(data);
// Step 2: Handle files
if (data.files) {
await fileManagementService.handleFileUpdates(
[],
data.files,
this.crudService.model,
item._id.toString(),
data.module_id
);
}
// Step 3: Update table order
await tableOrderService.addItemToTableOrder(
data.module_id,
item._id,
false
);
// Step 4: Log operation
await loggingService.logCreation(
item.title,
"ModuleName",
data.credential,
data.ip,
data.userAgent
);
return item;
}
}
Testing Services
// Example unit test for a service
import { CrudService } from "../services/crud.service";
import { Model } from "mongoose";
describe("CrudService", () => {
let crudService: CrudService<any>;
let mockModel: jest.Mocked<Model<any>>;
beforeEach(() => {
// Create mock model
mockModel = {
find: jest.fn(),
findById: jest.fn(),
create: jest.fn(),
// ... other methods
} as any;
crudService = new CrudService(mockModel);
});
it("should get items by id", async () => {
const mockItem = { _id: "123", title: "Test" };
mockModel.findById.mockResolvedValue(mockItem);
const result = await crudService.getById("123");
expect(mockModel.findById).toHaveBeenCalledWith("123");
expect(result).toBeDefined();
});
// More tests...
});
Benefits Summary
- Code Reusability: Services can be used across multiple controllers
- Testability: Services are easily testable in isolation
- Maintainability: Business logic is centralized and easier to modify
- Separation of Concerns: Controllers handle HTTP, services handle business logic
- Type Safety: Full TypeScript support with proper interfaces
- Error Handling: Centralized error handling in services
- Logging: Consistent logging patterns across the application