Modules
How NestForge uses modules to organize applications and manage dependencies.
A module is a class (struct) annotated with a #[module()] macro. The #[module()] macro provides metadata that NestForge uses to organize the application structure.
Overview
Section titled “Overview”Each application has at least one module, a root module. The root module is the starting point used to build the application graph—the internal data structure NestForge uses to resolve module and provider relationships and dependencies.
While very small applications might theoretically have only the root module, this is not the typical case. We want to emphasize that modules are strongly recommended as an effective way to organize your components. Thus, for most applications, the resulting architecture will employ multiple modules, each encapsulating a closely related set of capabilities.
graph TD AppModule --> UsersModule AppModule --> AuthModule AppModule --> ConfigModule UsersModule --> DbModule AuthModule --> UsersModuleThe #[module] Macro
Section titled “The #[module] Macro”The macro takes four properties that describe the module:
| Property | Description |
|---|---|
imports | The list of imported modules that export the providers which are required in this module. |
controllers | The set of controllers defined in this module which have to be instantiated. |
providers | The providers that will be instantiated by the NestForge injector and that may be shared at least across this module. |
exports | The subset of providers that are provided by this module and should be available in other modules which import this module. |
Basic Example
Section titled “Basic Example”#[module( imports = [DatabaseModule], controllers = [UsersController], providers = [UsersService], exports = [UsersService])]pub struct UsersModule;Provider Scopes
Section titled “Provider Scopes”In NestForge, providers can have different lifetimes. Choosing the right scope is essential for both performance and correctness.
Singleton (Default)
Section titled “Singleton (Default)”A single instance of the provider is shared across the entire application. It is instantiated during application bootstrap.
Request
Section titled “Request”A new instance of the provider is created for every incoming request. This instance is garbage collected after the request has finished processing. Use this when your service needs access to RequestContext (e.g., the current user).
Transient
Section titled “Transient”A new instance of the provider is created every time it is injected into another component.
The #[injectable] Macro
Section titled “The #[injectable] Macro”The recommended way to define providers is with the #[injectable] macro. This marks the struct as a managed provider and automatically implements Clone.
Basic Usage
Section titled “Basic Usage”use nestforge::prelude::*;
#[injectable]pub struct UsersService;This automatically:
- Implements the
Injectabletrait - Registers the provider using
Self::default()internally - Implements
Clonefor the service
Custom Factory
Section titled “Custom Factory”For custom initialization, use the factory attribute:
#[injectable(factory = build_service)]pub struct CustomService;
pub fn build_service() -> CustomService { CustomService { /* custom setup */ }}Manual Provider Registration
Section titled “Manual Provider Registration”You can also register providers manually in a module with the Provider enum:
use nestforge::prelude::*;
#[module( providers = [ Provider::value(app_config), Provider::factory(|container| { let store = container.resolve::<InMemoryStore<User>>()?; Ok(UsersService { store }) }), Provider::request_factory(|container| { let request_id = container.resolve::<RequestId>()?; Ok(RequestScopedService { request_id }) }), Provider::transient_factory(|container| { Ok(FreshService::new()) }) ])]pub struct AppModule;Provider::value(...)- Registers a direct valueProvider::factory(|container| ...)- Creates a singleton instanceProvider::request_factory(|container| ...)- Creates per-request instancesProvider::transient_factory(|container| ...)- Creates fresh instances on every resolve
Feature Modules
Section titled “Feature Modules”A feature module simply organizes code relevant for a specific feature, keeping code organized and establishing clear boundaries. This helps us manage complexity and develop with SOLID principles.
pub mod users_controller;pub mod users_service;
use nestforge::module;use self::users_controller::UsersController;use self::users_service::UsersService;
#[module( controllers = [UsersController], providers = [UsersService], exports = [UsersService])]pub struct UsersModule;Shared Modules
Section titled “Shared Modules”In NestForge, modules are singletons by default, and thus you can share the same instance of any provider between multiple modules effortlessly.
Once defined, the UsersService can be reused. To do so, we first need to export it, and then other modules can import UsersModule.
Global Modules
Section titled “Global Modules”If you have to import the same set of modules everywhere, it can get tedious. In NestJS-style frameworks, you can make a module Global.
#[module( providers = [ConfigService], exports = [ConfigService], global = true)]pub struct ConfigModule;Dynamic Modules
Section titled “Dynamic Modules”Dynamic modules allow you to create customizable modules that can register and configure providers dynamically. This is the equivalent of forRoot() or register() in NestJS.
impl DatabaseModule { pub fn for_root(options: DbOptions) -> DynamicModule { DynamicModule::builder() .module::<Self>() .providers([ Provider::value(options) ]) .exports([DbOptions]) .build() }}Lifecycle Hooks
Section titled “Lifecycle Hooks”Modules can hook into the application lifecycle to perform setup or cleanup tasks.
on_module_init(): Called once the module has been initialized.on_application_bootstrap(): Called once the entire application has started.on_module_destroy(): Called before the module is destroyed.on_application_shutdown(): Called before the application shuts down.
impl OnModuleInit for UsersService { async fn on_module_init(&self, container: &Container) -> anyhow::Result<()> { println!("UsersService initialized!"); Ok(()) }}