Skip to content
NestRS logo

You write the logic.
NestRS carries the rest.

The Rust backend framework with NestJS-style decorators — authentication, tenant isolation, field masking and transactions, verified at boot. ×4.2throughput241k req/s on one core · ×2.5 vs Fastify ×25less memory8 MB under load · NestJS ~200 MB ×23faster cold start~15 ms · NestJS ~350 ms ×3lower p990.42 ms · no GC pauses 0unguarded routesan ungated, unmasked surface cannot ship Per core against NestJS 11, byte-identical contract — reproducible from the repo. HTTPOpenAPIGraphQLWebSocketsMCP DatabaseQueueScheduleOpenTelemetry AuthenticationOAuth2Authorization

Authenticated, tenant-scoped, transactional, field-masked — and the impl block is empty on purpose. The two guards declare the posture, #[crud] generates the five routes.

crates/features/src/orgs/http/controller.rs (abridged)
#[controller(path = "/orgs")]
#[use_guards(AuthnGuard, AuthzGuard)]
pub struct OrgsController {
#[inject]
svc: Arc<OrgsService>,
}
#[crud(service = svc, entity = OrgEntity, output = Org,
create = CreateOrg, update = UpdateOrg)]
impl OrgsController {}

One ability decides the rows and the fields, in one place:

crates/features/src/authz/ability.rs (abridged)
ab.can(Action::Read, user::Entity)
.when(|p| p.eq(user::Column::OrgId, actor.org_id))
.fields([user::Column::Id, user::Column::Name]);

Same route, same handler — the caller’s token does the rest:

 curl :3002/users -H "authorization: Bearer $ADMIN"   # admin
[{ "id": "…ac00", "name": "Acme Admin", "email": "admin@acme.test", … }, …]

 curl :3002/users -H "authorization: Bearer $MEMBER"  # plain member
[{ "id": "…ac00", "name": "Acme Admin" }, …]   # email masked, org-scoped

 curl -o /dev/null -w "%{http_code}" :3002/users/<globex-id> -H "…$MEMBER"
403   # cross-tenant read refused, decided by the framework

Posture is mandatory, not a convention: a GraphQL operation with neither #[authorize] nor #[public] does not compile, and an HTTP controller whose guard wiring is missing fails at boot. Behind the guards sits the full stack — JWT on EdDSA keys, OAuth2 with PKCE, Argon2id passwords, social login — each a module import (Security).

See it live — the security checkpoint →

No wiring file, no config, no boilerplate. The hard parts of framework Rust — lifetimes in middleware, trait gymnastics, generic bounds — stay inside the framework; your files hold structs, decorators and async fns.

 nestrs new hello --standalone
Created standalone nestrs app at ./hello
  + hello/Cargo.toml
  + hello/rust-toolchain.toml
  + hello/.gitignore
  ..
  + hello/src/service.rs
  + hello/src/controller.rs
  + hello/tests/integration/main.rs
  + hello/tests/e2e/main.rs

Mode: standalone (one crate, logic in src/)

Next steps:
  cd ./hello
  nestrs run dev
  Open http://localhost:3000/ in your browser
hello/src/controller.rs
Write
use std::sync::Arc;
use nest_rs_http::{controller, routes};
use crate::service::HelloService;
#[controller(path = "/")]
pub struct HelloController {
#[inject]
svc: Arc<HelloService>,
}
#[routes]
impl HelloController {
#[get("/")]
#[public]
async fn hello(&self) -> String {
self.svc.greeting()
}
}
 nestrs run dev
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/hello`
2026-04-29T12:00:00.799422Z INFO nest_rs::module: module dependencies initialized module="HelloModule"
2026-04-29T12:00:00.799862Z INFO nest_rs::routes: GET    /  (hello)
  ..
2026-04-29T12:00:00.802100Z INFO poem::server: listening addr=socket://0.0.0.0:3000
2026-04-29T12:00:00.802552Z INFO poem::server: server started
2026-04-29T12:00:00.803011Z INFO nest_rs::access: method=GET path=/ status=200 bytes=11 duration_ms=0.189 client_ip=127.0.0.1 user_agent=curl/8.7.1 trace_id=48643283b6e9c222dadc23480117d76a

 curl http://localhost:3000
Hello World

As with any Rust dependency, the first nestrs run dev compiles the crate graph once. Every run after is the sub-second incremental rebuild shown above.

Scaffold a standalone crate, grow into a workspace when you add apps — the same inject-and-decorate model carries every surface: HTTP, GraphQL, WebSockets, queues, scheduled jobs, MCP. Get started → · Why NestRS →

The same service written twice, each side the way its own CLI scaffolds it, on both Fastify and Express. Both serve a byte-identical HTTP contract at the same stack depth, and a conformance gate verifies the bytes before anything is measured. One pinned core each — the regime that favours Node.

NestRS NestJS 11 · Node 24 (Fastify & Express)
Throughput 1 core · req/s
NestRS ~241k
Fastify ~97k
Express ~58k
×2.5
Latency p50 lower is better
NestRS 0.24 ms
Fastify 0.62 ms
Express 1.06 ms
×2.6
Latency p99 lower is better
NestRS 0.42 ms
Fastify 1.27 ms
Express 2.11 ms
×3.0
Memory under load lower is better
NestRS 8 MB
Fastify 198 MB
Express 206 MB
×25
Cold start lower is better
NestRS ~15 ms
Fastify ~360 ms
Express ~330 ms
×23
How these numbers were taken

The harness lives in bench/ — provider manifests, the byte-for-byte conformance gate, and the runner. Server and load generator (oha) are pinned to disjoint CPU sets; each tier gets a discarded warmup, then repeated timed runs whose medians are reported; every result embeds its machine fingerprint (CPU, kernel, toolchains, resolved dependency versions). Node runs NODE_ENV=production on Node 24; NestRS is a release build.

Per-core is the honest comparison: in production Node scales out by clustering — one ~200 MB process per core — while one NestRS binary takes every core in a single single-digit-MB process. The published run comes from a 4-core / 8 GB Docker VM (Apple Silicon) — virtualized, so these figures are a floor rather than a ceiling. One command reproduces the table on your hardware:

Terminal window
cd bench && just bench

The benchmark page documents the contract, the full protocol and its limits.

Secure & transactional by composition

Authn, authorization, row-level filtering, response masking, transaction scope — turned on by importing a module, never by remembering to call them.

Wiring you find out about at boot, not at 3am

The DI graph is checked at boot: a bad import fails startup with the missing wire named. No reflection, no resolution error five minutes after deploy.

Declarative, decorator-driven

#[module], #[controller], #[resolver], #[processor], #[gateway], #[scheduled], #[mcp] — expanding to plain Rust you can read with cargo expand.

Types you don't fight

Rust types end to end — entity, DTO, handler, dataloader, GraphQL schema, OpenAPI doc. No any, no unknown, no casts at the boundaries.

Native throughput

×2.5 NestJS-on-Fastify — its best case — and ×4.2 Express, with sub-millisecond p99 and no GC pauses (benchmarks).

An order of magnitude less RAM

~8 MB under load where the same NestJS service holds ~200 MB. Smaller instances, higher density, a materially lighter cloud bill.

Boots in milliseconds

Each deployable is a static native binary with no runtime to warm up — autoscaling and cold starts stop hurting.

Batteries included, opt-in by crate

HTTP, GraphQL, OpenAPI, WebSockets, queues, scheduling, events, MCP, authorization, health probes, OpenTelemetry — each an opt-in crate, so a worker compiles no HTTP stack at all (Packages).

Every capability above ships today, each with a reference section on this site.

Microservice scaling without the distributed-systems tax. Entities, services and policy are written once in a shared features crate; each app under apps/* is a thin composition root importing only the transports it serves, so a worker never compiles the HTTP stack. Self-contained tokens and a shared database — no chatty RPC, no service mesh to get started.

Who runs where is a composition choice in each app’s module.rs, not a fork of entities and services — schematically:

Publicexposed to clients and partners
  • App A:3000a public surface
  • App B:3001another public surface
shared featuresone model — every app above and below builds on it
Privatenon-public · off the client request path
  • App Cbackground work
  • App Dmore background work

Same domain, shared features — one monorepo or several; which app runs where is an operational choice, not a rewrite of business logic.

Each app scales horizontally on its own signal — a hot public surface follows demand, background apps follow queue depth. Same binary, more replicas, per app; no single “scale the monolith” knob.

App A:3000demand · high traffic×5
App B:3001demand · steady×2
App Cqueue · backlog×4
App Dqueue · light×2

One product, four apps — A scales on traffic spikes, B stays lean, C follows backlog, D only when needed.

  • Getting started — scaffold and run your first app.
  • Tutorial — build a posts feature end to end.
  • Why NestRS — the thesis and the structural properties behind it.
  • Threat model — the boundary each layer enforces, and how it composes with your deployment’s own controls.
  • The Publish workspace — the product universe every example is drawn from.

Built by YV17labs