Context & DI
Last Updated: May 19, 2025
This document describes the backend context and dependency injection (DI) system in the Typus Development Framework.
Overview
The Typus Development Framework uses a centralized context and dependency injection system to manage application state, services, and component lifecycle. This approach promotes:
- Loose coupling between components
- Testability through easy dependency mocking
- Singleton management for shared resources
- Automatic component registration via decorators
Dependency Injection
The framework uses TSyringe for dependency injection, enhanced with custom decorators for automatic component registration.
Component Registration
Components are automatically registered using decorators:
@Service()
export class UserService extends BaseService {
// Service implementation
}
@Controller()
export class UserController extends BaseController {
constructor(
@inject(UserService) private userService: UserService
) {
super();
}
// Controller implementation
}
Dependency Resolution
Dependencies are resolved automatically when components are instantiated:
// In a module
const controller = container.resolve(UserController);
const service = container.resolve(UserService);
Global Context
The framework maintains a global context with shared resources:
- Logger - Global logging instance
- Prisma - Database client
- Config - Application configuration
These resources are accessible throughout the application:
// Access global logger
this.logger.info('Message');
// Access global prisma client
const users = await this.prisma.user.findMany();
Singleton Management
Services are registered as singletons by default, ensuring that only one instance exists throughout the application lifecycle:
// In core/decorators/component.ts
container.registerSingleton(name, target);
Core services are explicitly registered as singletons during application initialization:
// In Application.ts
private registerSingletons(): void {
container.registerSingleton(DslService);
container.registerSingleton(DynamicRouterService);
// Other core services
}
Module Context
Each module maintains its own context, including:
- Base path - The module's API base path
- Controller - The module's controller instance
- Service - The module's service instance
- Router - Express router for the module
// In a module class
constructor() {
const basePath = 'users';
const controller = container.resolve(UserController);
const service = container.resolve(UserService);
super(basePath, controller, service);
}
Request Context
Each request has its own context, including:
- User - The authenticated user (if any)
- Validated data - Data that has passed validation
- Request-specific metadata - Headers, params, query, etc.
// In a controller method
async getUser(req: Request, res: Response) {
const { id } = req.params;
const user = req.user; // Authenticated user
const validatedData = this.getValidatedData(req);
return await this.userService.getUser(id);
}
Benefits
The context and DI system provides several benefits:
- Simplified component creation - No need to manually wire dependencies
- Consistent component lifecycle - Components are created and destroyed consistently
- Centralized resource management - Shared resources are managed in one place
- Improved testability - Dependencies can be easily mocked for testing
- Reduced boilerplate - Decorators handle registration and injection
Best Practices
- Use decorators for component registration
- Inject dependencies through constructor parameters
- Access global context through base classes
- Avoid circular dependencies by designing clear component hierarchies
- Use interfaces for dependency contracts when appropriate