Why NestRS
The thesis
Section titled “The thesis”Scalable Rust backend apps with native performance.
Cross-cutting concerns — security, transactions, input conversion, lifecycle, discovery — are error-prone exactly because they are repetitive. Every place a codebase has to wire them by hand is a place they will eventually be forgotten, inconsistent, or wrong. NestRS treats this as a framework problem: those concerns must be transparent to the application code.
The leverage is procedural macros. A controller, a resolver, a processor is a struct with a decorator; the framework expands it into the boilerplate a contributor would otherwise have written by hand — and would, eventually, have written slightly differently in two different files.
Why now
Section titled “Why now”Two shifts make this trade-off worth reopening.
Memory is now a line item. Cloud RAM has grown steadily more expensive, and for many services it is the largest single part of the bill. Managed runtimes — Node among them — are genuinely productive, but they buy that productivity with a garbage collector and a runtime whose footprint is always resident, whether or not a request is in flight. You pay for it idle, and you pay for it again in energy per request.
Native code is no longer the hard path. Much of the boilerplate that once made higher-level runtimes feel faster to ship — scaffolding, ceremony, repetitive wiring — is easier to absorb today. The friction that justified the runtime tax has largely dissolved.
NestRS reopens the trade-off between the two: keep the declarative,
decorator-driven style that makes the managed-runtime model productive, but stand
it on a native, compiled foundation that doesn’t bill you for it in RAM. One
cargo step compiles and type-checks, modules wire up regardless of import
order, and the result ships as one lean binary.
How it delivers
Section titled “How it delivers”Six consequences of the thesis — not a feature list. They compose: drop any one and the others weaken. Each section below states the guarantee; the mechanics live in Fundamentals, Security, and Database.
1. Declarative wiring
Section titled “1. Declarative wiring”Modules, providers, controllers, resolvers, gateways, processors — each is a struct decorated with an attribute macro. The decorator carries the entire integration contract. There is no service locator to call, no registration list to keep in sync, no central manifest a contributor has to remember to edit.
#[controller(path = "/users")]pub struct UsersController { #[inject] svc: Arc<UsersService>,}2. Secure by default
Section titled “2. Secure by default”Once the security modules are imported, every read through the data layer is filtered, every mutating write is gated, and every response body is masked. A feature does not opt in to authn and authz; it opts out by not importing them.
Security is structural, not vigilant: forgetting a check is a category error the framework prevents, not a bug a code reviewer has to catch.
3. Transactional by default
Section titled “3. Transactional by default”A mutating HTTP request installs a transactional executor
before the handler runs. The service reaches the database only through
Repo, which picks up that executor
automatically — commit on 2xx/3xx, rollback otherwise. Worker contexts
install a pool executor for the same code path.
// Ambient executor — no per-method ceremony.impl UsersService { pub async fn rename(&self, id: Uuid, name: String) -> Result<User, ServiceError> { let user = Repo::<Users>::find_by_id(id) .await? .ok_or_else(|| ServiceError::not_found("user"))?; let mut active = user.into_active_model(); active.name = Set(name); Ok(User::from(&Repo::<Users>::update(active).await?)) }}4. Multi-transport, one shape
Section titled “4. Multi-transport, one shape”A feature is a port (entity, service, contract) plus one adapter per
transport it exposes. The port sits at the feature root; each adapter lives in
its own sub-folder with its own module.rs. An app picks the edges it serves —
a worker imports the queue adapter; an API imports HTTP, GraphQL, and WS.
Directoryfeatures/users/
- module.rs, service.rs, entity.rs, dto.rs, error.rs
Directoryhttp/
- …
Directorygraphql/
- …
Directoryws/
- …
Directoryqueue/
- …
Directorymcp/
- …
See Modules — Port + adapters for the composition rule and the reference layout.
5. Wiring verified at boot
Section titled “5. Wiring verified at boot”The dependency-injection graph is not resolved by reflection. Every module
records its imports and its providers’ dependencies at compile time; at startup,
App::build() walks the graph and fails with a clear error if a provider
injects something its module cannot reach.
A misconfigured import is a startup error naming the missing dependency, not
a Cannot resolve at first request. See
Providers — The access graph.
6. Lean per-binary
Section titled “6. Lean per-binary”Capabilities ship as separate crates and integrate via discovery gated on module reachability. A binary that imports only the queue adapter does not mount HTTP routes or GraphQL resolvers — even when those files live in the same shared feature crate. One codebase, multiple deployable shapes.
On the introduction page, the same Hello World service benchmarks at roughly 25× the throughput and ~20× less RAM than an equivalent Node stack on identical hardware.
What this is not
Section titled “What this is not”- Not an HTTP layer. NestRS sits on top of
hyper/tokio/poem— it gives them structure, it does not replace them. - Not a thin convenience layer. The framework’s value is in the cross-cutting guarantees (authz, transactions, masking, boot-time wiring). A controller-only decorator on top of an existing stack would not deliver them.
- Not a runtime DI container. The container exists, but the contract it
enforces is static: types and module imports, checked at boot. There is no
resolve<T>()you are expected to call from user code.
Going further
Section titled “Going further”- Getting started — install, run an app, build your first feature.
- The Publish workspace — the fictional product every example app tells.
- Tutorial — a complete feature, end to end, with every layer wired.
- Fundamentals — modules, providers, guards, pipes, interceptors, filters.