Rust Service 03. Structuring a Rust Axum API project
Summary
A toy Axum example can live in main.rs. A service that will be operated needs separate startup code, routing code, state definitions, and handlers so changes stay visible.
There is no need to start with deep architecture. This post starts with a shallow main.rs, router.rs, state.rs, and handlers/ shape, then adds repository or service layers only when a database or external API creates a real boundary.
Curriculum Position
- Series: Rust Service to Production
- Previous post: Building a minimal API server with Axum
- Next post: Designing request and response types with serde
- Expansion criteria: before publication, add the example repository, commands, tool versions, and failure logs that fit this post’s scope.
Document Info / Environment
- Written date: 2026-05-04
- Verification date: 2026-05-05
- Document type: tutorial
- Test environment: No direct execution test. The structure below is a design target to verify in the example repository with file movement,
cargo test, andcargo run. - Tested versions: No runtime versions pinned. On the verification date, this was checked against the Rust Book package/crate rules and Axum
0.8.9documentation on docs.rs. - Evidence level: official documentation, original project documentation
Problem Statement
A one-file minimal server raises the next question: how far should the project be split? If everything stays in one file for too long, routes, state, and handlers become tangled. If too many layers appear too early, the post teaches folder names before the reader sees why they are needed.
This post covers structuring a Rust Axum API project. The rule is simple: keep the entry point small, assemble routes in one place, make shared state explicit, and keep handlers focused on turning HTTP input into responses.
Verified Facts
- The Rust Book describes a crate as the smallest amount of code the compiler considers at a time, and a package as one or more crates plus
Cargo.toml. Evidence: Rust Book: Packages and Crates - The Rust Book describes Cargo’s convention that
src/main.rsis the crate root of a binary crate andsrc/lib.rsis the crate root of a library crate. Keepingmain.rsas a small runtime entry point fits that convention. Evidence: Rust Book: Packages and Crates - Axum documentation describes
Routeras the type that connects paths to services or handlers. Separatingrouter.rsandhandlers/maps that framework boundary into the project layout. Evidence: Axum crate documentation - Axum documentation describes shared state through
Stateextraction and.with_state(...). A visiblestate.rstype is easier to review than hidden global handler dependencies. Evidence: Axum crate documentation main.rs,router.rs,state.rs, andhandlers/are a local design choice for this series, not an official Rust or Axum standard.
Reproduction Steps
There is no direct execution result yet. Before publication, move the minimal server into this shape and record cargo test and cargo run results.
src/
main.rs
router.rs
state.rs
handlers/
mod.rs
health.rs
Limit each file’s job.
main.rsreads configuration, opens the listener, and callsbuild_router.router.rsassembles route lists, common layers, and state wiring.state.rsdefines dependency types shared by handlers.handlers/contains functions that turn HTTP input into response types.- Do not add repository or service layers until a database, queue, or external API makes that boundary useful.
Before publication, use these checks.
cargo test
cargo run
Observations
- This document currently contains no actual
cargo testorcargo runoutput. - The success condition is that
/healthand the JSON echo endpoint keep the same behavior after the file split. - Failure notes should separate module path errors, visibility errors, state type mismatches, and missing routes.
Verification Checklist
- Does
main.rsavoid detailed handler logic? - Can the route list be found in one file?
- Is shared state explicit, without hidden global dependencies in handlers?
- Does each new layer reduce real complexity instead of only adding names?
- Are actual
cargo testandcargo runoutputs recorded after the file move?
Interpretation
Project structure is more like a tool for lowering change cost than a universal answer sheet. Starting with domain, application, infrastructure, and adapter folders can make the reader memorize names before the need exists.
This series starts shallow and adds layers only when SQLx storage, authentication, or external service calls create a reason. That way the reader learns “this much split is enough for this complexity,” not just “copy this folder tree.”
Limitations
- This post does not yet include actual file movement and execution output.
- The proposed shape is for the small API example in this series, not a standard layout for every Rust web service.
- Workspaces, multi-crate APIs, plugin architectures, and monorepo operations are outside the scope.
- Before publication, add an example repository, commands, versions, and failure logs.
References
Change Log
- 2026-05-04: Initial Rust Service to Production curriculum draft.
- 2026-05-05: Separated Rust package/crate evidence, Axum structure criteria, and pre-execution verification steps.
댓글남기기