Skip to content

Latest commit

 

History

History
275 lines (202 loc) · 11.7 KB

File metadata and controls

275 lines (202 loc) · 11.7 KB

Scaffolding

Rustango has two layers of code generation, so you rarely wire boilerplate by hand:

  1. The project generator — cargo rustango new creates a whole new project from a template.
  2. In-project generators — manage startapp and the manage make:* family add apps, views, serializers, jobs, and more inside an existing project.

cargo rustango new scaffolds a complete, ready-to-run project — Cargo manifest, config tiers, Docker, migrations, and src — in one command

Table of contents


Install the generator

cargo rustango is a Cargo subcommand. Install it once, globally:

cargo install cargo-rustango

That puts a cargo-rustango binary on your PATH; Cargo then exposes it as cargo rustango, one global command you can run from anywhere.

The generator's own version is the one your project gets

cargo rustango new writes rustango = "MAJOR.MINOR" into the generated Cargo.toml, taken from the generator's version, not from whatever is newest on crates.io. Whichever generator you install is the version your project pins.

That is almost always what you want, and it is why the install command above is unpinned: the newest generator writes the newest pin, and the two cannot drift apart.

It is worth knowing when you deliberately want an older release — to match a project already on it, or to reproduce a report. Pin the generator, not the project:

cargo install cargo-rustango --version 0.57.0

Check which one you have with cargo rustango --version. Upgrading later is the same command with --force, and it only affects projects you generate afterwards — an existing project's pin is a line in its own Cargo.toml, which you edit yourself.


Create a project: cargo rustango new

cargo rustango new <name> [--template api|fullstack|tenant]
                          [--backend postgres|sqlite|mysql]
                          [--features <list>]
  • <name> — the project (and crate) name. It must be a valid Cargo crate name ([A-Za-z_][A-Za-z0-9_-]*), and the target directory must not already exist.
  • --template / -t — which starter to scaffold (default: fullstack).
  • --backend / -b — which database the project runs on (default: postgres).
  • --features / -F — extra Rustango features, comma- or space-separated.
  • --interactive / -i — pick from menus instead. A bare cargo rustango new on a terminal does the same.
  • --help / -h, --version — usage and version.

Run it with no arguments and it asks:

  rustango — new project
  (enter accepts the default; ctrl-c aborts)

  Project name: shop

  Template
    1) fullstack  ORM + auto-admin + forms — the usual starting point  (default)
    2) api        bare ORM + axum, no admin UI — for JSON-only services
    3) tenant     multi-tenancy: tenant registry + operator console
  > 3

  Database
    1) postgres  every feature, including schema-mode tenancy  (default)
    2) sqlite    a file beside the project — no server to run
    3) mysql     MySQL 8.0+ / MariaDB
  > 1

  Extra features  (numbers, e.g. `1 3 4` — enter for none)
     1) csrf         CSRF protection middleware for form POSTs
     2) sso          OIDC single sign-on for application users
     …
  > 1 2

  Same thing without the wizard:
    cargo rustango new shop --template tenant --backend postgres --features csrf,sso

  Create it? [Y/n]

The wizard sets exactly the fields the flags set and prints the equivalent command line before it writes anything — so using it once teaches the flags, and there is only one code path deciding what a project contains. Off a terminal (a script, a CI job) it fails with a message instead of waiting for an answer nobody is there to give.

The three templates

Each maps to one of Rustango's three app shapes:

Template What you get Reach for it when
api Bare ORM + Axum, no admin JSON-only services and microservices
fullstack (default) ORM + the auto-admin A typical web app with a back-office
tenant Multi-tenancy + operator console + per-tenant apps SaaS hosting many isolated tenants
cargo rustango new myblog                      # fullstack (the default)
cargo rustango new api_demo  --template api
cargo rustango new shop      --template tenant

Choosing a database: --backend

cargo rustango new edge --backend sqlite
cargo rustango new shop --backend mysql

--backend decides what cargo run uses, and shapes the whole project to match — the DATABASE_URL in .env.example, the services in docker-compose.yml, the url in each settings tier, and the README's run instructions. Pick sqlite and there is no database service at all: it is a file beside the project, created by the first cargo run -- migrate.

All three backends stay wired up in the generated [features], so the other two remain one flag away:

cargo run --no-default-features --features sqlite

Turning on more of the framework: --features

cargo rustango new saas --template tenant --features csrf,sso,cache-redis

A template turns on a sensible set; --features adds the opt-ins none of them reach:

Feature What it adds
tenancy Multi-tenancy: tenant registry, per-tenant databases, operator console
csrf CSRF protection middleware for form POSTs
sso / admin-sso OIDC single sign-on, for application users / for the admin site
passkey WebAuthn / passkey authentication
cache-redis / cache-page Redis cache backend / whole-page response caching
jobs / jobs-postgres Background job queue, in-process / database-backed so it survives restarts
scheduler Fixed-interval background tasks
email-smtp SMTP transport for the email framework
mcp Model Context Protocol server for AI agents
testkit / test_utils Test-only schema builders, factories, and constructors

cargo rustango new --help prints this list. Backends are not valid here — pass --backend instead; naming one as a feature is refused, because it would pin the framework's backend while the project's own feature stayed off, and #[derive(Model)] gates its emissions on the project's features.


What gets generated

Every template writes a self-contained Cargo project:

<name>/
  Cargo.toml            # the rustango dependency + features for this template
  .env.example          # copy to .env (DATABASE_URL, RUSTANGO_SESSION_SECRET, …)
  .gitignore
  rust-toolchain.toml   # selects the `stable` toolchain + rustfmt/clippy/rust-analyzer
  docker-compose.yml    # a Postgres service to develop against
  Dockerfile            # production image
  README.md
  config/
    default.toml        # settings shared across every environment
    dev_settings.toml   # per-tier overrides …
    staging_settings.toml
    prod_settings.toml
  migrations/           # JSON migration files (committed to git)
  src/
    main.rs             # the single binary — HTTP server + every manage verb
    models.rs           # your #[derive(Model)] structs
    views.rs            # request handlers ("views")
    urls.rs             # pub fn api() -> Router that aggregates your routes

One binary for everything

src/main.rs is the only entrypoint. It boots the HTTP server and dispatches every manage verb — there is no separate manage script or src/bin/manage.rs:

mod models;
mod urls;
mod views;

#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();
    rustango::manage::Cli::new()
        .api(urls::api())
        .with_welcome()  // friendly `/` page until you add a root handler
        .with_health()   // /health + /ready endpoints (fullstack & tenant)
        .run()
        .await
}

So cargo run starts the server, and cargo run -- <verb> runs migrations, generators, and the rest.

How the templates differ inside main.rs / urls.rs:

  • api — no admin; urls::api() simply aggregates your own routes.
  • fullstack — same urls.rs, plus the admin feature compiled in. The admin is not wired up for you: nothing generated would call it, so the generator emits no admin_router. Add one yourself and nest it — getting started, Step 11 spells it out. Take rustango::sql::Pool so the helper names no driver.
  • tenant — main.rs adds .tenancy(), serving the operator console at the apex domain and each tenant under its own subdomain. The framework's own tables are generated into a system/migrations/ folder from the compiled models on the first cargo run -- migrate — no hand-shipped bootstrap JSON, so the very first migrate works with no extra setup.

Layered configuration

Settings load config/default.toml first, then config/<RUSTANGO_ENV>_settings.toml on top. RUSTANGO_ENV defaults to dev, so a freshly scaffolded cargo run works with no edits; set RUSTANGO_ENV=prod in production to pick up prod_settings.toml.

First run

cd <name>
cp .env.example .env
docker compose up -d        # start Postgres
cargo run -- migrate        # apply migrations
cargo run                   # serve
cargo run -- --help         # see every manage verb

Add a feature module: manage startapp

Scaffold a self-contained module of related models, views, and routes:

cargo run -- startapp blog

It writes src/blog/ containing mod.rs, models.rs (a starter model named after the singularized app — blog → Blog), views.rs, urls.rs, and tests.rs, then declares the module in src/main.rs and merges its routes into urls::api().

Options:

  • --into <dir> — scaffold under a base directory other than src/ (e.g. a workspace member).
  • --with-manage-bin — also emit a bin/manage.rs (for layouts that prefer a separate manage binary).

Generate single files: the make:* commands

Inside a project, the make:* verbs scaffold one file at a time. The full per-flag reference lives in the manage CLI reference; the common shapes are:

Command Generates
make:viewset <Name> [--model <M>] A CRUD ViewSet — the six REST routes over one model
make:serializer <Name> [--model <M>] A serializer for request/response shaping
make:api_routes <app> An API route aggregator for an app
make:form <Name> An HTML form with validation
make:job <Name> A background job handler
make:notification <Name> A multi-channel notification
make:middleware <Name> A middleware skeleton
make:test <Name> A test module using the in-process test client
cargo run -- make:viewset PostViewSet --model Post
cargo run -- make:serializer PostSerializer --model Post
cargo run -- make:test post_smoke

A typical flow

cargo rustango new myblog                              # 1. scaffold the project
cd myblog
cargo run -- startapp blog                             # 2. add a feature module
# …add fields to src/blog/models.rs…
cargo run -- makemigrations                            # 3. generate a migration
cargo run -- migrate                                   # 4. apply it
cargo run -- make:viewset PostViewSet --model Post     # 5. expose a JSON API
cargo run                                              # 6. serve