Base Classes
Last Updated: May 19, 2025
This document describes the core base classes of the Typus Development Framework, which provide the foundation for building application components.
Overview
The base classes in the Typus Development Framework provide common functionality and structure for application components. They implement patterns such as error handling, logging, dependency injection, and transaction management, allowing developers to focus on business logic rather than boilerplate code.
CoreBase
The CoreBase
class is the root class for all core components. It provides common initialization and logging functionality.
Key Features
- Automatic Logging: Initializes a logger instance for each component
- Class Name Tracking: Automatically captures the class name for logging
- Initialization Logging: Logs component initialization
Implementation
export abstract class CoreBase {
protected logger: Logger;
protected className: string;
constructor() {
// Initialize logger
this.logger = LoggerFactory.getGlobalLogger();
// Set class name for logging
this.className = this.constructor.name;
// Log initialization
this.logger.info(`[${this.className}] Initialized`);
}
}
Usage
All core components extend CoreBase
to inherit common functionality:
export class UserService extends CoreBase {
constructor() {
super(); // Initializes logger and sets className
// Component-specific initialization
}
}
BaseController
The BaseController
class provides a foundation for API controllers, handling common HTTP operations, error handling, and response formatting.
Key Features
- Automatic Error Handling: Wraps controller methods to catch and handle errors
- Response Formatting: Standardized methods for success and error responses
- Validation Integration: Built-in support for request validation
- Prisma Error Handling: Special handling for database-related errors
Controller Method Wrapping
The BaseController
uses a JavaScript Proxy to automatically wrap controller methods with error handling and logging:
Response Methods
The BaseController
provides standardized methods for different types of responses:
- success: Return successful response with data
- error: Return error response with message and code
- notFound: Return 404 Not Found response
- badRequest: Return 400 Bad Request response
- unauthorized: Return 401 Unauthorized response
- forbidden: Return 403 Forbidden response
Usage Example
export class UserController extends BaseController {
constructor(private userService: UserService) {
super();
}
async getUser(req: Request, res: Response) {
const userId = req.params.id;
const user = await this.userService.findById(userId);
if (!user) {
return this.notFound(res, 'User not found');
}
return this.success(res, user);
}
}
BaseService
The BaseService
class provides a foundation for service components, which implement business logic and orchestrate data operations.
Key Features
- Automatic Error Handling: Wraps service methods to catch and handle errors
- Transaction Support: Helper method for running operations in a transaction
- Context Access: Access to the current request context
- Structured Results: Standardized result format for service operations
Service Method Wrapping
Like BaseController
, the BaseService
uses a JavaScript Proxy to automatically wrap service methods with error handling and logging:
Service Result Interface
The BaseService
defines a standard result interface for service operations:
export interface ServiceResult<T> {
data?: T;
error?: BaseError;
}
Transaction Support
The BaseService
provides a helper method for running operations in a database transaction:
protected async withTransaction<T>(
callback: (tx: any) => Promise<T>
): Promise<T> {
return this.prisma.$transaction(async (tx) => {
return callback(tx);
});
}
Usage Example
export class UserService extends BaseService {
async createUser(userData: CreateUserDto): Promise<ServiceResult<User>> {
try {
const user = await this.withTransaction(async (tx) => {
// Create user
const newUser = await tx.user.create({
data: userData
});
// Create related records
await tx.profile.create({
data: {
userId: newUser.id,
// ...other data
}
});
return newUser;
});
return { data: user };
} catch (error) {
// Error handling is done by the proxy
throw error;
}
}
}
BaseRepository
The BaseRepository
class provides a foundation for data access components, defining a standard interface for CRUD operations.
Key Features
- Standard CRUD Interface: Defines standard methods for data operations
- Prisma Integration: Built-in access to the Prisma client
- Action Logging: Helper method for logging repository actions
Standard CRUD Methods
The BaseRepository
defines abstract methods for standard CRUD operations:
- create: Create a new entity
- findById: Find an entity by ID
- findAll: Find all entities, optionally with filters
- update: Update an existing entity
- delete: Delete an entity
Usage Example
export class UserRepository extends BaseRepository<User> {
async create(data: Partial<User>): Promise<User> {
this.logAction('create', { data });
return this.prisma.user.create({ data });
}
async findById(id: string): Promise<User | null> {
this.logAction('findById', { id });
return this.prisma.user.findUnique({
where: { id }
});
}
async findAll(params?: any): Promise<User[]> {
this.logAction('findAll', { params });
return this.prisma.user.findMany(params);
}
async update(id: string, data: Partial<User>): Promise<User> {
this.logAction('update', { id, data });
return this.prisma.user.update({
where: { id },
data
});
}
async delete(id: string): Promise<boolean> {
this.logAction('delete', { id });
await this.prisma.user.delete({
where: { id }
});
return true;
}
}
BaseModule
The BaseModule
class provides a foundation for application modules, handling route registration and initialization.
Key Features
- Route Management: Manages module routes and controllers
- Authentication Integration: Built-in methods for authentication and authorization
- Dependency Injection: Integrates with the DI container
- Initialization Hooks: Abstract methods for module initialization
Module Structure
Abstract Methods
The BaseModule
defines abstract methods that must be implemented by concrete modules:
- initialize: Perform module initialization
- initializeRoutes: Set up module routes
Usage Example
export class UserModule extends BaseModule {
constructor() {
super(
'/users', // Base path
UserController, // Controller
UserService // Service
);
}
protected initialize(): void {
// Module initialization logic
}
protected initializeRoutes(): void {
// GET /users
this.router.get('/', this.controller.getAllUsers);
// GET /users/:id
this.router.get('/:id', this.controller.getUserById);
// POST /users
this.router.post('/', this.controller.createUser);
// Protected route example
this.router.put(
'/:id',
this.auth(), // Require authentication
this.roles(['admin', 'user']), // Require specific roles
this.controller.updateUser
);
}
}
BaseError
The BaseError
class provides a foundation for application errors, with standardized properties and serialization.
Key Features
- Error Classification: Standard properties for error type, code, and status
- Context Support: Additional context data for debugging
- Stack Trace Capture: Automatic capture of stack traces
- JSON Serialization: Conversion to JSON for API responses
Error Hierarchy
Standard Error Properties
- message: Human-readable error message
- code: Machine-readable error code
- status: HTTP status code
- context: Additional context data
Usage Example
// Throwing a standard error
throw new NotFoundError('User not found', { userId: '123' });
// Creating a custom error
class UserExistsError extends BadRequestError {
constructor(email: string) {
super(`User with email ${email} already exists`, { email });
this.code = 'USER_EXISTS';
}
}
// Using the custom error
throw new UserExistsError('[email protected]');
BaseQueue
The BaseQueue
class provides a foundation for queue workers, handling asynchronous task processing.
Key Features
- Task Processing: Methods for processing queue tasks
- Error Handling: Standardized error handling for tasks
- Retry Logic: Support for task retries
- Logging: Comprehensive logging of task processing
Queue Processing Flow
Usage Example
export class EmailQueue extends BaseQueue {
constructor(private emailService: EmailService) {
super('email-queue');
}
async process(job: any): Promise<void> {
const { to, subject, body } = job.data;
try {
await this.emailService.sendEmail(to, subject, body);
this.logger.info(`Email sent to ${to}`);
} catch (error) {
this.logger.error(`Failed to send email to ${to}`, { error });
throw error; // Will trigger retry logic
}
}
}
Inheritance and Extension
The base classes are designed to be extended and customized as needed. Developers can override methods, add new functionality, or create specialized subclasses.
Extension Example
// Extended controller with additional functionality
export class ApiController extends BaseController {
protected paginate<T>(
res: Response,
items: T[],
total: number,
page: number,
limit: number
): Response {
return this.success(res, {
items,
pagination: {
total,
page,
limit,
pages: Math.ceil(total / limit)
}
});
}
}
// Using the extended controller
export class ProductController extends ApiController {
async getProducts(req: Request, res: Response): Promise<Response> {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 10;
const { items, total } = await this.productService.findPaginated(page, limit);
return this.paginate(res, items, total, page, limit);
}
}
Best Practices
When working with base classes, follow these best practices:
- Don't Bypass the Proxy: Let the proxy handle errors and logging
- Use Typed Parameters: Leverage TypeScript for type safety
- Keep Methods Focused: Each method should have a single responsibility
- Use Appropriate Error Types: Choose the right error class for each situation
- Leverage Transactions: Use transactions for operations that modify multiple records
- Follow the Pattern: Maintain consistency with the established patterns
Conclusion
The base classes in the Typus Development Framework provide a solid foundation for building application components. By extending these classes, developers can create consistent, maintainable, and robust applications with minimal boilerplate code.
The base classes implement common patterns and best practices, allowing developers to focus on business logic rather than infrastructure concerns. They provide a balance of structure and flexibility, enabling both rapid development and customization when needed.