Skip to main content
Providers are the services, repositories, and utilities that your application logic depends on. In nestrs, any type can become a provider by implementing the Injectable trait — the #[injectable] proc-macro generates that implementation for you. The framework constructs and caches providers in the ProviderRegistry, then makes them available to controllers and other providers through Axum’s State extractor.

Marking a type as injectable

Apply #[injectable] to a struct to generate the Injectable trait implementation. nestrs calls construct to build the type, injecting any dependencies it finds in the registry:
use nestrs::prelude::*;
use std::sync::Arc;

#[injectable]
pub struct AppService {
    prisma: Arc<PrismaService>,
}
nestrs infers dependencies from the struct fields. Any field of type Arc<T> where T is a registered provider is resolved from the registry automatically.
construct is synchronous. Do not call block_on or perform I/O inside it — use on_module_init for async setup.

Provider scopes

Control how long a provider instance lives with the scope argument:
// Default: one shared instance per application
#[injectable]
pub struct SingletonService;

// New instance on every injection site / registry.get() call
#[injectable(scope = "transient")]
pub struct TransientService;

// One instance per HTTP request (requires use_request_scope())
#[injectable(scope = "request")]
pub struct RequestScopedService;
ScopeLifetimeNestJS equivalent
"singleton" (default)One per application containerScope.DEFAULT
"transient"New instance per resolutionScope.TRANSIENT
"request"One per HTTP requestScope.REQUEST

Registering providers in a module

List your providers in the providers field of the enclosing module. Providers that other modules need must also appear in exports:
#[module(
    controllers = [AppController, AppControllerV2],
    providers   = [AppService],
)]
pub struct AppModule;
To share a provider across modules:
#[module(
    controllers = [UserController],
    providers   = [UserService],
    exports     = [UserService],
)]
pub struct UserModule;

#[module(
    imports  = [UserModule],
    providers = [AppService],
)]
pub struct AppModule;

Injecting providers into controllers

Controllers access their providers via Axum’s State extractor. The #[routes(state = T)] macro wires the service type into the router:
#[routes(state = AppService)]
impl AppController {
    #[get("/")]
    pub async fn root(State(service): State<Arc<AppService>>) -> &'static str {
        service.get_hello()
    }

    #[get("/db-health")]
    pub async fn db_health(
        State(service): State<Arc<AppService>>,
    ) -> Json<DbHealthResponse> {
        Json(service.db_health().await)
    }
}

Injecting providers into other providers

When one provider depends on another, declare the dependency as an Arc<T> field and ensure both types are registered in the same module (or that the dependency is exported from an imported module):
#[injectable]
pub struct AppService {
    prisma: Arc<PrismaService>,
}

impl AppService {
    pub async fn list_users(&self) -> Result<Vec<UserRow>, String> {
        self.prisma
            .query_all_as(r#"SELECT "id", "email", "name" FROM "User""#)
            .await
    }
}

Lifecycle hooks

Injectable provides four async hooks (all default to no-ops):
use nestrs::prelude::*;
use async_trait::async_trait;

#[injectable]
pub struct DatabaseService {
    // fields ...
}

#[async_trait]
impl Injectable for DatabaseService {
    fn construct(registry: &ProviderRegistry) -> Arc<Self> {
        Arc::new(Self { /* wire fields */ })
    }

    async fn on_module_init(&self) {
        // Run after all singletons are constructed; safe to do async I/O here.
    }

    async fn on_application_shutdown(&self) {
        // Close connections, flush buffers.
    }
}
Lifecycle hooks only run for singleton providers. Transient providers are constructed on demand and do not receive hook calls.

Custom factory providers

Use register_use_factory when construction order must be explicit or when you need to close a provider dependency cycle without calling registry.get() eagerly inside another type’s construct:
use nestrs::core::{ProviderRegistry, ProviderScope};
use std::sync::Arc;

fn register_heavy(registry: &mut ProviderRegistry) {
    registry.register_use_factory::<HeavyService>(ProviderScope::Singleton, |reg| {
        let light = reg.get::<LightService>();
        Arc::new(HeavyService::new(light))
    });
}

All custom provider variants

MethodNestJS analogueNotes
register_use_value::<T>(Arc<T>)useValuePre-built singleton; no lifecycle hooks
register_use_factory::<T>(scope, |reg| Arc<T>)useFactorySync closure; call reg.get() for dependencies
register_use_class::<T>()useClassSame as register::<T>() for normal #[injectable] types
Keep factory closures non-async. Defer I/O to on_module_init on the produced type, or to ConfigurableModuleBuilder::for_root_async for module-level configuration.