Fundamentals
Modules, providers, guards, pipes, interceptors, filters — the vocabulary every NestRS app is built from.
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_http(&mut Request)returnsErr(Denial)to short-circuit the chain, or 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. The domain lives in crates/features/, where
every app can reach it; the app crate under apps/ only wires modules
together, which is why no service.rs or controller.rs appears there.
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::blog::BlogService;
#[controller(path = "/")]pub struct BlogController { #[inject] svc: Arc<BlogService>,}
#[routes]impl BlogController { #[get("/")] #[public] 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. #[public] is the route’s posture — every route declares
one, and an unguarded route with no posture is flagged at boot
(unguarded routes detected). This tagline is open on purpose, so it says so;
Guards covers the other half.
use nest_rs::core::module;use nest_rs::http::{HttpConfig, HttpModule};use features::blog::BlogController;use features::blog::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: unmet dependency: `BlogController` (in module `BlogModule`) depends on `BlogService`, but no module provides it and it is not global infrastructure (a seed or factory output). Add a provider for `BlogService` to a module reachable from the root, or seed it at `App::builder()`.The error names the consumer (BlogController), the missing dependency
(BlogService), and the two ways to fix it. Boot fails inside
build().await, before the HTTP transport starts — never at the first
request.
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.
Each primitive below gets its own page — concept, signature, where to bind it, and a tested example. Handler types (controllers, resolvers, gateways, processors, MCP tools) are transport-specific and live in their own categories: HTTP, GraphQL, WebSockets, Queue, MCP.
In this section
Section titled “In this section”Basics
Section titled “Basics”- Modules — the composition unit,
imports, and the port + adapters layout. - Providers — injectables, scopes, and the boot-time access graph.
- Guards — pre-handler gates, the three scopes, and declaring on the provider.
- Pipes — parse, normalize and validate at the request boundary.
- Interceptors — wrap handler execution, install ambient state, shape the response.
- Error handling — typed catches, unconditional mapping, and the defaults underneath.
All options
Section titled “All options”- Lifecycle —
#[hooks]on the init and shutdown phases the framework drains in order. - The request lifecycle — one pool per
layer family, deduplicated by
TypeId, run exactly once. - Middleware — why there is no middleware primitive, and which of the two roles you actually want.
- Correlation — the W3C trace every unit of work runs under, readable anywhere.
- Extending NestRS — the extension contract and its three selection modes.
Going further
Section titled “Going further”- Architecture and naming — the role tables, the five naming levels, and the reserved vocabulary.
- Tutorial — build the composed
posts/root end to end. - HTTP — the first transport to mount on this vocabulary.