Skip to main content
Modules are the fundamental unit of organization in nestrs. Every nestrs application has at least one root module, and larger applications divide their code into feature modules — each encapsulating a related set of controllers, providers, and imports. The #[module] proc-macro generates the Module and ModuleGraph trait implementations that NestFactory uses to build the provider registry and Axum router.

The #[module] macro

Annotate a plain struct with #[module] to define a module. The macro accepts four optional fields:
Modules that re-export another module’s providers use re_exports:

Composing feature modules

1

Define a feature module

Create a module for a focused area of your application. List only the providers and controllers that belong to that feature.
2

Import the feature module from your root module

List the feature module in imports. Exported providers are absorbed into the importing module’s registry.
3

Bootstrap the application

Pass the root module to NestFactory::create. nestrs builds the full provider registry and Axum router automatically.

Dynamic modules

Static #[module] declarations cover most use cases. When you need to configure a module at runtime — loading options from environment variables, a remote secrets store, or feature flags — use a DynamicModule.

DynamicModule::from_module

Converts any static module into a DynamicModule at call time:

ConfigurableModuleBuilder

The ConfigurableModuleBuilder pattern mirrors NestJS’s forRoot / forRootAsync:

Lazy modules

DynamicModule::lazy::<M>() runs M::build() at most once per process and shares the singleton cells across all callers. This is useful for shared infrastructure modules (databases, caches) that multiple feature modules import independently.

Namespaced configuration (#[config(namespace)])

For config that should come from environment variables instead of hardcoded for_root values, decorate a plain struct with #[config(namespace = "db")]:
The macro emits the required serde::Deserialize + validator::Validate derives and the nestrs::ConfigNamespace marker impl, so the struct itself needs no derives — but the attribute must come before any derives you add yourself. Values are read from NESTRS_DB__HOST / NESTRS_DB__PORT (prefix NESTRS_, double-underscore separator between namespace and key). The overlay merges dotenvy cascade files (.env, .env.<NESTRS_ENV>, in non-production only) with the process environment last, so config resolution is testable without mutating process env. Register typed namespaces as modules:
Inside any #[injectable], resolve the typed config via ConfigService:
Validation runs on load: a struct whose field fails its validator attribute surfaces a ConfigError at startup (fail-fast) rather than a garbled value deep in a request handler. For a single non-namespaced struct, nestrs::config::load_config::<T>() reads flat env vars (HOST, PORT) with the same dotenvy cascade + validation behavior.

Lifecycle sequence

When you call NestFactory::create and then listen or listen_graceful, nestrs drives the provider registry through the following sequence for all singleton providers:
1

eager_init_singletons()

Constructs every singleton in the registry synchronously. This is where circular provider dependency panics surface.
2

run_on_module_init().await

Calls on_module_init on every singleton. Use this hook for async setup (opening database pools, warming caches) that must complete before the server accepts traffic.
3

run_on_application_bootstrap().await

Calls on_application_bootstrap on every singleton. Use this for tasks that depend on all modules being fully initialized.
4

Server accepts requests

The Axum TCP listener opens and the application begins serving traffic.
5

Shutdown: run_on_application_shutdown().await

On SIGTERM or graceful shutdown, on_application_shutdown is called on every singleton.
6

Shutdown: run_on_module_destroy().await

on_module_destroy is called last. Use this to close connections and flush buffers.
Lifecycle hooks only run for singleton providers. Transient providers are constructed on demand and do not participate in the global hook sequence.

Circular module imports

If two modules import each other, nestrs detects the cycle and panics with:
Mark the back-edge import with forward_ref to break the cycle:
When possible, extract shared types into a third module and import that from both sides instead of introducing a circular reference.