Scaffold the app
You add the blog app to your workspace with the nestrs CLI —
the same tool from Getting started. By the end of this
page, nestrs run dev blog listens on the port pinned in module.rs and
answers Hello World on GET / — the scaffold proves itself before you write
a line.
Create the app
Section titled “Create the app”From the workspace root:
-
Scaffold the app and the feature it serves.
Terminal window nestrs new blogInside an existing workspace this writes
apps/blog/and picks the next free HTTP port inmodule.rs(seenestrs new blogin the CLI docs). The command refuses to overwrite if the folder already exists. -
Start it under
bacon(restarts on save).Terminal window nestrs run dev blog -
Hit the root route — use the port from
apps/blog/src/module.rs.Terminal window $ curl -i http://localhost:3005/HTTP/1.1 200 OKHello World
Need the full CLI surface — layout detection, --check, generators?
See the CLI reference.
What the CLI created
Section titled “What the CLI created”nestrs new blog does not ask you to hand-edit the root Cargo.toml
members list — workspace apps are discovered from apps/*. It writes a
thin app plus the feature behind that greeting: main.rs boots the
framework, module.rs composes transports and imports one feature module, and
there is no controller.rs or service.rs in the app crate — those live in
crates/features/, which is where posts lands from the next page onward.
Directoryapps/blog/
- Cargo.toml
Directorysrc/
- lib.rs
- main.rs
- module.rs
Directorytests/
- integration/main.rs
- e2e/main.rs
Directorycrates/features/src/blog/
- mod.rs
- module.rs
- service.rs the greeting
Directoryhttp/
- mod.rs
- module.rs
- controller.rs
#[public] GET /
The blog feature is a placeholder with one job: proving the wiring works
end to end on the first run. Delete it once posts is serving something real.
[package]name = "blog"version.workspace = trueedition.workspace = truepublish = false
[dependencies]features.workspace = truenest-rs-core.workspace = truenest-rs-config.workspace = truenest-rs-http.workspace = truetokio.workspace = trueanyhow.workspace = true
[dev-dependencies]nest-rs-testing.workspace = trueThe root module is where transports come in. HttpModule::for_root pins
the listen port in code — not in .env:
use nest_rs_core::module;use nest_rs_http::{HttpConfig, HttpModule};
use features::blog::BlogHttpModule;
#[module( imports = [ HttpModule::for_root(HttpConfig { port: 3005, ..Default::default() }), BlogHttpModule, ],)]pub struct BlogModule;The reference workspace pins 3005 for blog. When you scaffold with
nestrs new blog, the CLI picks the next free port — yours may differ
if other apps already exist in the workspace.
BlogModule has no providers = [...] list. A root module composes
feature modules and lets each own its providers — the
reference root
carries only imports. Note the two BlogModules: this one is the app’s
composition root, and features::blog::BlogModule is the feature’s own. Two
crates, no collision — the app only ever names the …HttpModule edge.
use anyhow::Result;use nest_rs_config::Environment;use nest_rs_core::App;
use blog::BlogModule;
#[tokio::main]async fn main() -> Result<()> { let _environment = Environment::init();
App::builder() .module::<BlogModule>() .build() .await? .run() .await}App::builder() runs the four boot phases — seeds, collect, factories,
register — verifies the access graph, then .run() blocks on the
transports. The CLI also writes src/lib.rs (re-exporting BlogModule
for the tests in Test it end to end), a smoke test that
asserts the greeting, and an empty tests/e2e/main.rs. The e2e suite has to
exist even while empty: nestrs run test unit filters on not binary(e2e), and
nextest rejects a filterset naming a binary the workspace does not have.
Run it again
Section titled “Run it again”-
From the workspace root:
Terminal window $ nestrs run dev blogCompiling blog v0.1.0Running `target/debug/blog`INFO nest_rs::routes: mounted route controller=BlogController method=GET path=/ handler=helloDEBUG nest_rs::http: transport listening addr=0.0.0.0:3005 tls=falseThe route table prints on every boot, and the
DEBUGline shows the port from yourmodule.rs. -
In another terminal:
Terminal window $ curl -i http://localhost:3005/HTTP/1.1 200 OKHello World -
Stop the server with
Ctrl-C.
That 200 is the checkpoint: the transport is mounted, the DI graph resolved,
the access graph passed, and a controller reached your browser. Everything after
this page replaces the greeting with real work.
What you have now
Section titled “What you have now”- A
blogapp underapps/blog/, scaffolded bynestrs new blog. - A
BlogModulethat activates the HTTP transport and imports one feature. - A
blogfeature answeringHello Worldon/— proof the framework boots and serves end to end.
Going further
Section titled “Going further”- Declare the entity — the next step: define the
Postentity with#[expose]. - CLI — the reference for
nestrs newand the rest of the CLI surface.
Built by YV17labs