Skip to content

Fundamentals

A NestRS app is a tree of modules. Each module groups providers (injectable structs) and optionally imports other modules. The dependency-injection graph is built at compile time and verified at boot — a missing wire fails startup with a clear error, never with a deep Cannot resolve at first request.

This category walks blog through the Publish story — from an inline root (step ①: BlogService / BlogController in apps/blog/, no database) to a composed API with a posts/ feature, the stage the tutorial builds in the repo — and the primitives every binary leans on:

  • Modules — the composition unit. #[module] declares what a module owns and what it depends on.
  • Providers — the things modules own. Anything #[injectable] (services, controllers, …) is built once and shared as Arc<T>.
  • Lifecycle — the moments an app comes alive and winds down. #[hooks] tags async methods for the init and shutdown phases the framework drains in order. See Lifecycle.
  • Guards — decide who gets through. Guard::check(&mut Request) short-circuits the chain with a Response or attaches request-scoped context the handler reads back via Ctx<T>.
  • Pipes — convert and validate input at the request boundary. Stateless transforms (Parse<T>, ParseUuid, ValidationPipe<T>, …) bound by HTTP through the Valid<E> / Piped<P, E> extractors.
  • Interceptors — wrap handler execution. The same seam that lets you log a request also installs the ambient database transaction and the authorization context.
  • Error handling — map errors to responses. #[use_filters(...)] for route-shaped mapping; #[use_exception_filters(...)] for typed catches. See Error handling.
  • Middleware — NestRS doesn’t have a middleware primitive. See the page for the Guards/Interceptors split if you’re looking for one.

Step ① of the blog story — handlers inline in the app root:

apps/blog/src/service.rs
use nest_rs_core::injectable;
#[injectable]
#[derive(Default)]
pub struct BlogService;
impl BlogService {
pub fn tagline(&self) -> &'static str {
"A minimal blog API"
}
}

A service is an injectable struct. Anything decorated with #[injectable] is registered with the container and shared as Arc<T>.

apps/blog/src/controller.rs
use std::sync::Arc;
use nest_rs_http::{controller, routes};
use crate::service::BlogService;
#[controller(path = "/")]
pub struct BlogController {
#[inject]
svc: Arc<BlogService>,
}
#[routes]
impl BlogController {
#[get("/")]
async fn root(&self) -> &'static str {
self.svc.tagline()
}
}

A controller is also a provider. #[inject] fields are resolved by type from the container; #[routes] mounts the verb methods on the controller’s path.

apps/blog/src/module.rs
use nest_rs_core::module;
use nest_rs_http::{HttpConfig, HttpModule};
use crate::controller::BlogController;
use crate::service::BlogService;
#[module(
imports = [HttpModule::for_root(HttpConfig { port: 3005, ..Default::default() })],
providers = [BlogService, BlogController],
)]
pub struct BlogModule;
apps/blog/src/main.rs
use nest_rs_core::App;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
App::builder()
.module::<BlogModule>()
.build()
.await?
.run()
.await
}
Terminal window
$ curl http://localhost:3005/
A minimal blog API

Later pages move the domain into posts/ (PostsService, PostsController, PostsModule, …) and grow BlogModule into a composed root — see Modules. The tutorial is where you implement that composed root in the repo.

  1. Collected every provider in the import tree — BlogService, BlogController — into a link-time registry.
  2. Built the access graph from BlogModule. BlogController declares it injects BlogService; BlogModule owns both; reachable. ✓
  3. Constructed each provider once. BlogService::default() produced one Arc<BlogService>, injected into BlogController::svc.
  4. Mounted BlogController on the HTTP transport from the route table #[routes] generated.

Comment the line that lists BlogService in BlogModule’s providers, then build:

Terminal window
Error: cannot register provider(s) — unreachable in graph:
- BlogController (needs BlogService)

The error names what is missing and why it cannot be resolved. The build does not start the HTTP transport when wiring is invalid.

  • Module — a struct decorated with #[module], listing providers it owns and imports it depends on. Modules walks blog from here to a composed root.
  • Provider — any injectable struct (#[injectable], #[controller], …). Built once, shared as Arc<T>. Providers follows the same posts/ split.
  • #[inject] field — declares “I need this type from the container.”
  • Access graph — boot-time check that proves every #[inject] resolves to a provider the module can reach.
  • Discoverable — every decorated provider registers itself; transports consume the registry filtered by reachability.

Each primitive gets its own page — concept, signature, where to bind it, and a tested example.

Fundamentals/
├─ Modules
├─ Providers
├─ Lifecycle
├─ Guards
├─ Pipes
├─ Interceptors
├─ Error handling
└─ Middleware (equivalence note)

Handler types (controllers, resolvers, gateways, processors, MCP tools) are transport-specific — they live in their own categories (HTTP, GraphQL, WebSockets, Queue, MCP).