Loco skeleton loco new, the route table, and the controller generator
A generator writes files you now own. Read the route table before you trust a URL.
A generator writes files you now own. Read the route table before you trust a URL.
§I. Frame
Sunday built a health endpoint by hand on Tokio. Loco is the product path: an opinionated Rust framework on Axum and Tokio, with generators, config per environment, workers, and test helpers. This is the first B-day row, so the job is the skeleton only. SeaORM is the next B-day.
Done bar: generate an app with no database, add one controller with the generator, read the route table, pass the generated tests, and hit the endpoint with curl.
§II. Three techniques
Choose the Starter on Purpose (named technique). loco new --help (loco 1.2.0) lists the choices that shape the whole app:
| Flag | Values | Today |
|---|---|---|
--db | sqlite, postgres, none | none (SeaORM is next B-day) |
--bg | async, queue-redis, queue-postgres, queue-sqlite, blocking | async |
--assets | serverside, clientside, none | none (Asr owns the frontend) |
Pass them as flags so the run is repeatable; the interactive prompt hides what you chose.
The Route Table Is the Truth (named technique). cargo loco routes prints every route the app will serve. Read it before writing a client.
A Generator Writes Code You Own (named technique). cargo loco generate controller health printed what it touched:
added: "src/controllers/health.rs"
injected: "src/controllers/mod.rs"
injected: "src/app.rs"
added: "tests/requests/health.rs"
injected: "tests/requests/mod.rs"
Two new files, three edits. Review the diff like a teammate's PR. It also printed that --api is no longer needed in 1.0 because generators adapt to the app.
§III. Run it
cargo install loco --locked
loco new --name duha_loco --db none --bg async --assets none --os macos
cd duha_loco
cargo loco generate controller health
The generated handler returned format::empty(). One edit makes it match Sunday's binary:
#[debug_handler]
pub async fn index(State(_ctx): State<AppContext>) -> Result<Response> {
format::json(serde_json::json!({ "status": "ok" }))
}
pub fn routes() -> Routes {
Routes::new().prefix("api/health/").add("/", get(index))
}
cargo loco routes (real):
/_health GET /_health
/_ping GET /_ping
/_readiness GET /_readiness
/api
├─ GET /api
└─ /health GET /api/health
cargo test: requests::home::can_get_home ... ok, requests::health::can_get_health ... ok, 2 passed.
PORT=5151 cargo loco start, then curl (real):
/api/health -> 200 {"status":"ok"}
/api/health/ -> 404 (HTML error page)
/_health -> 200 {"ok":true}
The prefix in code is api/health/, yet the table lists /api/health and the trailing-slash URL returns 404. Trust the table and the curl, not the string in the source. The config comment also warns that every Loco app defaults to port 5150, hence PORT=5151.
§IV. What the skeleton gives you
src/app.rs: theHooksimpl (boot, routes, workers, tasks). Routes start fromAppRoutes::with_default_routes(), which is where/_pingand/_healthcome from.config/{development,test,production}.yaml: templated withget_env, soPORTandLOG_LEVELcome from the environment..cargo/config.toml:loco = "run --", an alias.cargo locoiscargo run --under that alias, not a separate cargo plugin (contrast TRPL ch14.5).Cargo.tomldeclares a[workspace]with[workspace.dependencies]forloco-rs(TRPL ch14.3).
§V. Drills
- Your client calls
/api/health/and gets 404. First command? (cargo loco routes.) - Which flag would you change on the next B-day to add SeaORM? (
--db sqliteor--db postgres, on a fresh app.) - Where do
/_pingand/_healthcome from? (AppRoutes::with_default_routes().) - Two Loco apps on one machine fail to start. Why? (Both default to port 5150; set
PORT.)
Close
Choose the Starter on Purpose, The Route Table Is the Truth, A Generator Writes Code You Own. Generate, review the diff, read the table, curl it. Then add the database.
Related
- Tome: TRPL ch14.3 (grounded-in), ch14.5 (referenced)
- Web: Loco tour · controllers · loco_rs docs
- Prior Duha: Tokio runtime and the hand-built health binary (2026-10-11)