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 replies
404 to GET / (no routes mounted yet — that’s the next page).
Create the app
Section titled “Create the app”From the workspace root:
-
Scaffold a thin app — composition only, no routes yet.
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:<port>/# → 404 Not Found
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: main.rs boots the framework, module.rs composes
transports, no controller.rs or service.rs in the app crate (those
land in crates/features/ from page 2 onward).
Directoryapps/blog/
- Cargo.toml
Directorysrc/
- lib.rs
- main.rs
- module.rs
Directorytests/
- e2e.rs
[package]name = "blog"version.workspace = trueedition.workspace = truepublish = false
[dependencies]features.workspace = truenest-rs-core.workspace = truenest-rs-config.workspace = truenest-rs-http.workspace = truenest-rs-opentelemetry = { workspace = true, features = ["http"] }tokio.workspace = trueanyhow.workspace = true
[dev-dependencies]nest-rs-testing = { workspace = true, features = ["opentelemetry"] }The 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 nest_rs_opentelemetry::OpenTelemetryModule;
#[module(imports = [ OpenTelemetryModule, HttpModule::for_root(HttpConfig { port: 3005, ..Default::default() }),])]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.
use anyhow::Result;use nest_rs_config::Environment;use nest_rs_core::App;use nest_rs_opentelemetry::OpenTelemetry;
use blog::BlogModule;
#[tokio::main]async fn main() -> Result<()> { let _environment = Environment::init(); let _telemetry = OpenTelemetry::init("blog")?;
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 e2e test on page 8) and a stub tests/e2e.rs.
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`DEBUG nest_rs_http::transport: http transport listening addr=0.0.0.0:3001The log line shows the port from your
module.rs. -
In another terminal:
Terminal window $ curl -i http://localhost:3001/HTTP/1.1 404 Not Found -
Stop the server with
Ctrl-C.
A 404 is the right answer — the HTTP transport is mounted, the process
is healthy, and no route table claims / yet. That’s the gap the next
page fills.
What you have now
Section titled “What you have now”- A
blogapp underapps/blog/, scaffolded bynestrs new blog. - A
BlogModulethat activates the HTTP transport. - A running binary returning
404on the root route — proof the framework boots end to end.