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

BaseController

BaseService

BaseRepository

BaseModule

BaseQueue

BaseError

NotFoundError

BadRequestError

UnauthorizedError

ForbiddenError

ValidationError

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:

ServiceController MethodController ProxyExpress.jsClientServiceController MethodController ProxyExpress.jsClientalt[Success][Error]HTTP RequestRoute to controllerLog method callExecute methodCall serviceReturn resultReturn resultFormat success responseThrow errorLog errorFormat error responseReturn error responseHTTP Response

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:

RepositoryService MethodService ProxyControllerRepositoryService MethodService ProxyControlleralt[Success][Error]Call service methodLog method callExecute methodData operationReturn resultReturn resultReturn resultThrow errorLog errorConvert to BaseErrorReturn error result

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

BaseModule

Express Router

Controller

Service

Auth Middleware

Routes

API Endpoints

Authentication

Role-based Authorization

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

BaseError

NotFoundError

BadRequestError

UnauthorizedError

ForbiddenError

ValidationError

ResourceNotFoundError

InvalidOperationError

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

Task HandlerQueue WorkerMessage QueueTask ProducerTask HandlerQueue WorkerMessage QueueTask Produceralt[Can Retry][Max Retries Exceeded]alt[Success][Error]loop[Process Tasks]Enqueue TaskDequeue TaskTask DataProcess TaskTask CompletedAcknowledge TaskTask FailedRequeue TaskMove to Dead Letter Queue

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:

  1. Don't Bypass the Proxy: Let the proxy handle errors and logging
  2. Use Typed Parameters: Leverage TypeScript for type safety
  3. Keep Methods Focused: Each method should have a single responsibility
  4. Use Appropriate Error Types: Choose the right error class for each situation
  5. Leverage Transactions: Use transactions for operations that modify multiple records
  6. 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.

WARNING

Failed to fetch dynamically imported module: https://typus.dev/assets/RecursiveNavItem-Cep7andh.js

{ "stack": "AppError: Failed to fetch dynamically imported module: https://typus.dev/assets/RecursiveNavItem-Cep7andh.js\n at https://typus.dev/assets/index-DS79FI73.js:315:420\n at dn (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:13:1385)\n at We (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:13:1455)\n at Ws.t.__weh.t.__weh (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:14:7364)\n at jt (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:13:1866)\n at v (https://typus.dev/assets/vue-vendor-Ct83yDeK.js:14:4019)\n at https://typus.dev/assets/vue-vendor-Ct83yDeK.js:14:4097" }