CLI
The nestrs CLI scaffolds projects in two layouts — standalone
(one crate) or workspace (monorepo with
crates/features/ + apps/*). nestrs new picks the layout automatically
from the directory tree — no mode flags, no config file. Generated code follows
the same decorators as the framework repo (users/ for features,
api/ for composition apps).
Install
Section titled “Install”cargo install --locked nest-rs-clinestrs versionRequires Rust 1.96+ (edition 2024). That single install is all you need: the
first time you call nestrs run, the CLI installs the dev
toolchain it drives — just, bacon, cargo-nextest — once. Run nestrs doctor after install to check the Rust toolchain and list optional NESTRS_*
environment variables.
Layout detection
Section titled “Layout detection”nestrs new infers where you are and what to create. There is no
nestrs-cli.yaml — the workspace shape is already declared in the root
Cargo.toml (members = ["crates/*", "apps/*"]). The CLI walks up from the
current directory (or -o path) and reads that manifest.
| Where you run | Command | Result |
|---|---|---|
| Outside a nestrs workspace | nestrs new hello | Monorepo at ./hello/ + apps/hello/ on port 3000 |
| Inside a nestrs workspace | nestrs new blog | Thin app at apps/blog/ — next free port in module.rs |
| Anywhere (explicit) | nestrs new hello --standalone | Single crate at ./hello/ |
If apps/blog/ already exists, the CLI stops with app blog already exists — it does not overwrite.
Standalone vs workspace
Section titled “Standalone vs workspace”Standalone (--standalone) | Workspace (default) | |
|---|---|---|
| Layout | One crate — logic in src/ | Monorepo — crates/features/ + apps/* |
| Deps | nest-rs-* from crates.io | Same, via [workspace.dependencies] |
| Grow | Copy patterns by hand | nestrs g feature / g resource / g <transport>, nestrs new <app> |
| When | One binary, no shared product crate | Several apps sharing features |
Create a project
Section titled “Create a project”nestrs new hellocd hellonestrs run dev helloOpen http://localhost:3000/ — Hello World. Logic in
crates/features/src/hello/; apps/hello/ composes modules only.
Directoryhello/
- Cargo.toml
- Justfile
- test.just
- db.just
- compose.yml
- .env
- .env.development
- .env.example
- .gitignore
- rust-toolchain.toml
- README.md
Directorycrates/
Directoryfeatures/
- Cargo.toml
Directorysrc/
Directoryhello/
- http/…
- service.rs
- lib.rs
Directorymigrations/ SeaORM migrations + the
migratebinary- …
Directoryseed/ the
seedbinary- …
Directoryapps/
Directoryhello/
Directorysrc/
- lib.rs
- main.rs
- module.rs
Directorytests/
- integration/main.rs
- e2e/main.rs
The e2e suite is scaffolded empty. It has to exist: nestrs run test unit
filters on not binary(e2e) and nestrs run test e2e on binary(e2e), and
nextest rejects a filterset naming a binary the workspace does not have.
Grow the workspace (from the workspace root or any sub-folder):
nestrs new blognestrs g resource postsnestrs new hello --standalonecd hellonestrs run devOpen http://localhost:3000/ — Hello World. Service and controller live
in src/ (no features crate).
Directoryhello/
- Cargo.toml
- Dockerfile
- .dockerignore
- Justfile
- test.just
- .env
- .env.development
- .env.example
Directorysrc/
- controller.rs
- service.rs
- module.rs
- main.rs
Directorytests/
- integration/main.rs
- e2e/main.rs
No db.just here: the database verbs drive the workspace’s migrations and
seed crates, which a single crate has nowhere to put. g resource and
g migration are workspace-only for the same reason.
Dockerfile and .dockerignore ship in standalone mode only.
rust-toolchain.toml pins Rust 1.96 in both modes.
One starter, no template flag
Section titled “One starter, no template flag”There is nothing to choose. Every path out of nestrs new writes the same
hello module — a service with a greeting and one #[public] GET / that
returns it:
| What you run | What you get | GET / |
|---|---|---|
nestrs new acme | monorepo + hello feature + apps/hello/ on 3000 | 200 Hello World |
nestrs new blog (inside one) | blog feature + apps/blog/ on the next free port | 200 Hello World |
nestrs new solo --standalone | one crate, service.rs + controller.rs | 200 Hello World |
A freshly created project has to prove it started, and a 404 proves nothing to
someone looking at a browser — so there is no routeless variant to pick. Delete
the feature once you have a real one; nestrs new blog refuses if a blog
feature already exists, rather than overwriting it.
Ports: the CLI scans existing apps/*/src/module.rs and pins the next free one
in code (same pattern as auth → 3001, api → 3002, …). Workspace .env files
do not set NESTRS_HTTP__PORT — ports live in each app’s module.rs:
HttpModule::for_root(HttpConfig { port: 3002, ..Default::default() })Optional: --check runs cargo check after generation.
Add an app to an existing workspace
Section titled “Add an app to an existing workspace”From the workspace root (or apps/, crates/features/, …):
nestrs new blognestrs run dev blogCreates a thin app under apps/blog/ — composition root only, no
service.rs / controller.rs in the app crate — plus the blog feature it
serves under crates/features/src/blog/. That split is why the greeting is a
feature rather than a controller in the app: the layout keeps no business logic
in an app crate, so the same rule that governs your code governs the starter.
Running tasks
Section titled “Running tasks”Every project task runs through nestrs run <recipe> — the single front
door. It forwards the recipe and its arguments verbatim to
just, which reads the Justfile the
scaffolder wrote:
nestrs run dev # watch mode (rebuild + restart on save)nestrs run test unit # unit + integrationnestrs run db up # apply migrationsnestrs run # list the available recipesThe recipes are yours to edit. Add your own to the Justfile and they run the
same way — nestrs run deploy, nestrs run docker-up, whatever you write. The
framework owns the recipes it ships; you own the rest.
The first nestrs run installs the toolchain it drives (just, bacon,
cargo-nextest) once. On CI, where you provision tools yourself, set
NESTRS_NO_BOOTSTRAP=1 or pass --no-bootstrap — a missing tool then fails
with the manual install command instead of fetching anything.
Release binaries land in target/release/. nestrs run start compiles and
starts the server; use nestrs run build when you only need the artifact
(Docker, CI, or copying the binary elsewhere).
nestrs run build # default app (hello)nestrs run build blog # after nestrs new blognestrs run build --all # every app in the workspacenestrs run start hello # build + run in releasenestrs run build # target/release/hellonestrs run start # build + run in releaseThe standalone Dockerfile runs cargo build --release in a
multi-stage image — same output path as nestrs run build.
Generate features and adapters
Section titled “Generate features and adapters”Monorepo only. Generators scaffold under crates/features/src/<name>/, register
pub mod … in crates/features/src/lib.rs, and — when you run them from inside
an app — wire the edge module into that app’s module.rs. Every command takes
--dry-run (print what would change, write nothing) and a -p path
override (default: auto-discover from the current directory). Re-running a
generator is safe: it refuses to overwrite an existing feature or adapter, and
the wiring edits are idempotent.
A port is the transport-agnostic core (service + module). An adapter bolts one transport onto a port. A resource is a DB-backed CRUD port plus its HTTP adapter in one shot.
# Port only (service + module, no transport)nestrs g feature posts
# DB-backed CRUD: #[expose] entity + CrudService + guarded #[crud] controllernestrs g resource posts
# The app's authn/authz adapter — Claims, AuthnGuard, AppAbility, AuthzGuardnestrs g auth
# A SeaORM migration, registered in lib.rs and migrator.rsnestrs g migration create_posts
# Bolt a transport onto an existing portnestrs g http posts # controllernestrs g graphql posts # resolvernestrs g ws posts # gatewaynestrs g queue posts # processornestrs g schedule posts # scheduled tasksnestrs g mcp posts # MCP toolEach adapter delegates to the port’s service, so a freshly-generated port plus
any adapter compiles immediately — the handler is the seam you fill in. The
generator prints the import line and the app-level module each transport needs
(HttpModule, GraphqlModule, ScheduleModule, …).
When the cursor isn’t inside an app, wire the edge module by hand:
use features::posts::PostsHttpModule;
#[module(imports = [ // … PostsHttpModule,])]pub struct ApiModule;Why g resource is guarded
Section titled “Why g resource is guarded”g resource emits the #[crud] + #[use_guards(AuthnGuard, AuthzGuard)] form,
and scaffolds the g auth adapter when the workspace has none. That is not
hardening you can defer: Repo filters every read by the caller’s ambient
Ability, which only the ability guard installs, so
an unguarded DB-backed controller mounts its routes and then serves nothing.
A generated resource therefore answers 401 without a token and 403 until
AppAbility grants a rule for its entity — the two
lines the generator prints. A resource meant to be readable without a login is
the same slice with one route marked #[public] and a rule in
define_visitor; see Public reads.
Before inventing a second layout, open
crates/features/src/orgs/
side by side with what the generator emitted.
Doctor
Section titled “Doctor”nestrs doctorChecks:
rustc≥ 1.96 andcargoonPATH- whether the current directory sits inside a nestrs workspace
- optional env vars (
NESTRS_DATABASE__URL,NESTRS_QUEUE__URL, …)
Use it after install and before running DB- or Redis-backed apps.
Version, about, and update
Section titled “Version, about, and update”nestrs version# → NestRS x.y.z
nestrs about# → version, tagline, docs, repository, license, author
nestrs update# → checks crates.io; skips when already on the latest version
nestrs update --force# → cargo install --force nest-rs-cli (reinstall even at the same version)Contributors inside the monorepo clone:
nestrs update --from-path# → cargo install --locked --path crates/nest-rs-cli --forceCommand reference
Section titled “Command reference”| Command | Description |
|---|---|
nestrs new <name> | Monorepo at ./<name>/, or app at apps/<name>/ when already inside one |
nestrs new <name> --standalone | Single crate in ./<name>/ |
nestrs new <name> --check | Run cargo check after scaffolding |
nestrs doctor | Toolchain and env sanity check |
nestrs version | Print the CLI version |
nestrs about | Print project metadata |
nestrs update | Install latest CLI from crates.io when a newer version exists |
nestrs update --force | Reinstall from crates.io even when already on the latest version |
nestrs update --from-path | Reinstall from monorepo crates/nest-rs-cli |
nestrs g feature <name> | Transport-agnostic port in crates/features/src/ |
nestrs g resource <name> | DB-backed CRUD port + guarded HTTP adapter (deps auto-added) |
nestrs g auth | The app’s authn/authz adapter — one per workspace |
nestrs g migration <name> | SeaORM migration, registered in lib.rs and migrator.rs |
nestrs g http|graphql|ws|queue|schedule|mcp <name> | Add one transport adapter to a port |
Every generator accepts --dry-run and -p <path>.
Aliases: nestrs g = nestrs generate.
Going further
Section titled “Going further”- Getting started — first run after
nestrs new. - Tutorial — build
postsinapps/blog/, HTTP only. - Fundamentals / Modules — how root modules compose features.
Built by YV17labs