What it is
You write business logic; the framework carries the rest — authn, authz, row-level tenant filtering, per-field masking, transactions, discovery, and lifecycle. Those concerns are transparent to your code and verified at boot, not left to review discipline.
A full authenticated, tenant-scoped, transactional, field-masked CRUD resource
is an empty impl block:
#[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 {}
The differentiator is structural multi-tenant isolation you cannot forget:
row-level filtering, response masking, and transactions become non-optional the
moment the security modules are imported. A feature opts out by not importing
them. In NestJS, Spring, Rails, axum, or Loco, tenant filtering is discipline —
a scope you remember, a middleware you apply, a review comment. In NestRS it is
structural: the data layer applies it from the caller's ability, and an
operation with no declared access posture is refused — at compile time on
GraphQL, at boot on HTTP. MCP gets the same guarantee across the whole
protocol, not tools/call alone: scope, ability and transaction reach
prompts/get, resources/read, completion and the tasks/* trio, so a
Repo-backed prompt is row-filtered exactly like a controller.
The numbers behind the tagline — against the same hello-world service in
NestJS 11, idiomatic on both sides, byte-identical HTTP contract,
conformance-gated: NestRS runs in ~25× less memory (single-digit MB
against ~200 MB), cold-starts ~23× faster (milliseconds against near half
a second), and — core for core, each server pinned to a single CPU — serves
×4.2 the throughput of NestJS on Express (its default) — ×2.5 its best
case, Fastify — with p99 latency cut to a third. Scaling out changes nothing:
Node adds ~200 MB of process per core; one NestRS binary takes every core in
a single single-digit-MB process.
Where the Express service needs three boxes, one NestRS binary carries the
load. Those figures come from a 4-core / 8 GB Docker VM — virtualized,
so they are a floor, not a ceiling. The harness lives in bench/ —
cd bench && just bench reproduces every figure on your own hardware.
Try it on your machine:
cargo install --locked nest-rs-cli
nestrs new hello --standalone && cd hello
nestrs run dev # → Hello World on :3000
Adding NestRS to a crate you already have is one dependency. Every
capability — http, graphql, seaorm, ws, mcp, queue, schedule, … —
is a feature of the umbrella, and a feature you leave off is code you never
compile. No decorator ever asks you to declare a second crate:
cargo add nest-rs --features http,seaorm
→ Why not axum? · Coming from NestJS · Why NestRS
What ships
Every concern below is a module you import and a Cargo feature you can leave off — a headless worker compiles no HTTP stack. Each has a reference section on nestrs.dev.
| Concern | What ships |
|---|---|
| Transports | HTTP controllers with versioning, extractors, uploads, streaming · OpenAPI 3 + Swagger UI from the route table · GraphQL with dataloaders, subscriptions and federation · WebSocket gateways · MCP tools, prompts and resources |
| Data | SeaORM entities, Repo, ambient transactions, pagination, migrations, seeding · S3-compatible object storage |
| Security | JWT on EdDSA keys, OAuth2 with PKCE, Argon2id passwords, social login · abilities, row-level filtering, per-field masking, one policy across every transport |
| Background work | Redis-backed queues with retries, cron schedules, a typed in-process event bus |
| Platform | Typed config with an .env cascade · liveness/readiness/startup probes · rate limiting · OpenTelemetry logs, traces, metrics · Server-Timing |
| Tooling | The nestrs CLI — scaffold, generate a feature or an adapter, run, migrate · an in-process test harness booting the real DI graph |
What holds it up
Claims a clone of this repository lets you check:
- 66 decorators, one per integration contract, each expanding to plain Rust
you can print with
cargo expand— the index is on nestrs.dev/decorators. - 1,800+ tests across the framework workspace, with the end-to-end suites running against live Postgres, Redis and S3 rather than mocks — the dev container brings all three up before you get a shell.
- Conformance joins — the families the framework declares (decorator pairs,
for_rootseams, thewarn-level events that record a denial) are derived from the source and joined against the suites covering them, so a member no test names fails the suite (crates/nest-rs-conformance/). - Documentation gated against the code — 120+ pages, and a linter reading
the framework's own source for config key tables, trait signatures, version
pins and the imports a snippet needs to compile
(
docs/scripts/lint-docs.mjs).
Stability
NestRS is stable and ready for production. The public API is frozen for the
current major: breaking changes wait for the next one. Every nest-rs-* crate
publishes at the same version in lockstep, so one NestRS version names exactly
one compatible resolution — down to the third-party crates that surface in your
own signatures (poem, sea-orm, async-graphql, rmcp, schemars), whose
majors are tied to the NestRS major and frozen for the whole line.
Coming from the previous major? CHANGELOG.md lists what moved:
an edge is now two decorators — #[resolver] / #[mcp] on the struct,
#[operations] / #[tools] on the impl; a guard bound where its check never
runs no longer compiles; McpModule::for_root takes one value; a WebSocket
message declares its posture; and an auto-resolved HasMany relation is a Relay
Connection rather than a capped Vec.
Documentation
Using NestRS? Head to nestrs.dev — getting started, tutorial, why NestRS, the axum comparison, and one section per capability crate.
Contributing to the framework? This README is your entry point. For design
rules and conventions, read CLAUDE.md and
CONTRIBUTING.md.
Contributing
Anyone who can clone the repo can iterate on the framework — the dev container brings up Rust, Postgres and Redis in one step.
Get the dev container running
- Install Docker and the VS Code Dev Containers extension.
- Open the repo in VS Code and accept Reopen in Container.
cd demo && nestrs run dev api— the main Publish API onhttp://localhost:3002(runnestrs run db upfirst).
The container provisions the Rust toolchain and dev tooling (just, bacon,
cargo-nextest, …), and brings up Postgres and Redis beside it with
NESTRS_DATABASE__URL / NESTRS_QUEUE__URL already pointed at them. nestrs run dev
runs under bacon — every save triggers an incremental rebuild and a restart.
The runnable apps live in their own workspace under demo/ — cd demo
first; that directory is where nestrs run, the .env cascade, and the
database/test recipes resolve.
Prefer a local toolchain? See Getting started → Scaffold and start.
Project layout
Two Cargo workspaces, split along the framework/product line.
nestrs/
├─ crates/ the framework — nest-rs (the umbrella apps install)
│ │ over one nest-rs-* compilation unit per capability
│ ├─ nest-rs-core/ IoC container, modules, DI, bootstrap
│ ├─ nest-rs-http/ REST controllers & routing
│ └─ … (members = ["crates/*"])
├─ docs/ the nestrs.dev site (Astro Starlight)
└─ demo/ the product — its own workspace, consumes the framework
├─ apps/ one runnable binary each (the Publish workspace)
│ ├─ auth/ OAuth2 / JWT token issuer
│ ├─ api/ REST + GraphQL + OpenAPI, persisted & authorized
│ ├─ assistant/ Model Context Protocol server
│ ├─ live/ real-time WebSocket gateway
│ └─ worker/ background jobs & scheduling (headless)
├─ crates/
│ ├─ features/ product features — port + adapters (users, posts, authn, …)
│ ├─ migrations/ shared-database SeaORM migrations (CLI)
│ └─ seed/ shared-database demo data (CLI)
├─ Justfile, db.just, test.just, .env*, Dockerfile
└─ (members = ["apps/*", "crates/*"])
The demo/ workspace references the framework by relative path — one
workspace dependency, nest-rs = { path = "../crates/nest-rs", features = [ …] }, exactly the shape a real app has — so it builds against the live
framework source. You cd demo and drive it as if it were the app's own
repository — see demo/README.md for running the apps, the
command table, the Publish map, and Docker.
crates/nest-rs-*/— the framework: generic, product-agnostic building blocks, reached through thenest-rsumbrella rather than named one by one.demo/apps/<name>/—main.rs+module.rslisting the edge modules the binary serves.demo/crates/features/— the product's vertical slices; apps import the edges they serve.
Adding an app means a directory under demo/apps/; a new feature means a folder
under demo/crates/features/src/; a new framework capability means a nest-rs-*
crate under crates/. The hello and blog layouts are generated on
demand by nestrs new rather than checked in, so they never drift from the
framework — see Getting started and the
tutorial.
Running the apps
Everything runnable lives in demo/ — cd demo first, then
nestrs run (no args lists every recipe). The full command table, the Publish
app map, and the Docker build are documented in
demo/README.md.
Community & contributing
NestRS is stable and actively developed — contributors shape where it goes next, and you don't have to write Rust to help.
- 💬 Ask a question, propose an idea, or just say hi in Discussions.
- 🐛 Report a bug or request a feature through issues.
- 🌱 Pick up a
good first issue— CONTRIBUTING.md is the short path from idea to merged PR. - 🗺️ See what just shipped in the changelog.
- 🔒 Found a vulnerability? Follow SECURITY.md — please don't open a public issue for it.
If NestRS is useful to you, a ⭐ helps other Rust teams find it.
License
MIT — see LICENSE.
No comments yet
Be the first to share your take.