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_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, in the standalone shape — one crate, handlers
under src/. (A workspace app crate keeps no service.rs / controller.rs:
those live in crates/features/, which is where step ② moves them.)
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("/")] #[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 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: 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.
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).
Going further
Section titled “Going further”- Modules — the composition unit and port + adapters layout.
- Providers — injectables, scopes, and the access graph.
- Request lifecycle — how guards, pipes, and interceptors nest.
- Tutorial — build the composed
posts/root in the repo.
Built by YV17labs