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 asArc<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 aResponseor attaches request-scoped context the handler reads back viaCtx<T>. - Pipes — convert and validate input at the request boundary.
Stateless transforms (
Parse<T>,ParseUuid,ValidationPipe<T>, …) bound by HTTP through theValid<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.
A minimal app, end to end
Section titled “A minimal app, end to end”Step ① of the blog story — handlers inline in the app root:
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>.
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.
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;use nest_rs_core::App;
#[tokio::main]async fn main() -> anyhow::Result<()> { App::builder() .module::<BlogModule>() .build() .await? .run() .await}$ curl http://localhost:3005/A minimal blog APILater 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.
What the framework did
Section titled “What the framework did”- Collected every provider in the import tree —
BlogService,BlogController— into a link-time registry. - Built the access graph from
BlogModule.BlogControllerdeclares it injectsBlogService;BlogModuleowns both; reachable. ✓ - Constructed each provider once.
BlogService::default()produced oneArc<BlogService>, injected intoBlogController::svc. - Mounted
BlogControlleron the HTTP transport from the route table#[routes]generated.
Try breaking it
Section titled “Try breaking it”Comment the line that lists BlogService in BlogModule’s providers,
then build:
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.
Five ideas to remember
Section titled “Five ideas to remember”- Module — a struct decorated with
#[module], listingprovidersit owns andimportsit depends on. Modules walksblogfrom here to a composed root. - Provider — any injectable struct (
#[injectable],#[controller], …). Built once, shared asArc<T>. Providers follows the sameposts/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.
What follows
Section titled “What follows”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).