Skip to content

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).

From the workspace root:

  1. Scaffold a thin app — composition only, no routes yet.

    Terminal window
    nestrs new blog

    Inside an existing workspace this writes apps/blog/ and picks the next free HTTP port in module.rs (see nestrs new blog in the CLI docs). The command refuses to overwrite if the folder already exists.

  2. Start it under bacon (restarts on save).

    Terminal window
    nestrs run dev blog
  3. 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.

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
apps/blog/Cargo.toml
[package]
name = "blog"
version.workspace = true
edition.workspace = true
publish = false
[dependencies]
features.workspace = true
nest-rs-core.workspace = true
nest-rs-config.workspace = true
nest-rs-http.workspace = true
nest-rs-opentelemetry = { workspace = true, features = ["http"] }
tokio.workspace = true
anyhow.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:

apps/blog/src/module.rs
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.

apps/blog/src/main.rs
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.

  1. From the workspace root:

    Terminal window
    $ nestrs run dev blog
    Compiling blog v0.1.0
    Running `target/debug/blog`
    DEBUG nest_rs_http::transport: http transport listening addr=0.0.0.0:3001

    The log line shows the port from your module.rs.

  2. In another terminal:

    Terminal window
    $ curl -i http://localhost:3001/
    HTTP/1.1 404 Not Found
  3. 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.

  • A blog app under apps/blog/, scaffolded by nestrs new blog.
  • A BlogModule that activates the HTTP transport.
  • A running binary returning 404 on the root route — proof the framework boots end to end.

Next: declare the User entity →