Project Setup
Structure the API.
Project Setup is a free Learn Rust Coding lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Building a REST API in Rust
In this course you will build a small REST API in Rust. We use the Axum web framework, which is built on Tokio (async runtime) and Tower (middleware). It is ergonomic, type-safe, and widely used in production.
This first lesson sets up the project structure so later lessons can add routes, models, a database, and tests.
Creating the Project
Start with Cargo. A binary project gives you a src/main.rs entry point:
cargo new rest_apicreates the folder.cd rest_apimoves into it.cargo runbuilds and runs.
These are shell and cargo commands, not runnable Rust snippets.
// terminal
// cargo new rest_api
// cd rest_api
// cargo runAdding Dependencies
An Axum API needs a few crates in Cargo.toml:
axumfor routing and handlers.tokiofor the async runtime.serdefor JSON serialization.
// Cargo.toml
// [dependencies]
// axum = "0.7"
// tokio = { version = "1", features = ["full"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"The async Runtime
Web servers handle many connections concurrently, so Axum is async. The #[tokio::main] attribute turns an async main into a real entry point by starting the Tokio runtime. Every handler can use .await for non-blocking I/O.
// src/main.rs
use tokio;
#[tokio::main]
async fn main() {
println!("runtime started");
}A Minimal Server
The smallest Axum app builds a Router, binds a TCP listener, and serves. A single route maps GET / to a handler that returns a string. Handlers are just async functions returning something that implements IntoResponse.
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(root));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn root() -> &'static str {
"Hello, API!"
}How Routing Works
A Router maps a path and HTTP method to a handler. Chain .route(path, method(handler)) calls to register endpoints. Method helpers like get, post, put, and delete come from axum::routing. You can combine methods on the same path.
use axum::{routing::{get, post}, Router};
async fn list() -> &'static str { "list" }
async fn create() -> &'static str { "created" }
fn build_router() -> Router {
Router::new()
.route("/items", get(list).post(create))
.route("/health", get(|| async { "ok" }))
}Recommended Module Layout
As the API grows, split code into modules instead of one giant main.rs:
main.rs— startup and server wiring.routes.rs— router definition.handlers.rs— request handlers.models.rs— data structures.
This separation keeps each file focused and testable.
// src/main.rs
mod routes;
mod handlers;
mod models;
#[tokio::main]
async fn main() {
let app = routes::build();
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await.unwrap();
axum::serve(listener, app).await.unwrap();
}Shared Application State
Most APIs need shared state, such as a database pool or in-memory store. Axum holds it with .with_state(state) on the router. Handlers receive it via the State extractor. The state must be Clone; wrap mutable data in Arc and a lock.
use axum::{routing::get, Router, extract::State};
use std::sync::{Arc, Mutex};
type Db = Arc<Mutex<Vec<String>>>;
async fn count(State(db): State<Db>) -> String {
let n = db.lock().unwrap().len();
format!("{} items", n)
}
fn build(db: Db) -> Router {
Router::new().route("/count", get(count)).with_state(db)
}Returning JSON
To send JSON, wrap a serializable value in axum::Json. With serde deriving Serialize on your structs, Axum sets the correct content type and body automatically.
use axum::Json;
use serde::Serialize;
#[derive(Serialize)]
struct Status {
service: String,
healthy: bool,
}
async fn health() -> Json<Status> {
Json(Status { service: "api".into(), healthy: true })
}Configuration and Ports
Hardcoding the port is fine for demos, but real services read configuration from the environment. Use std::env::var with a default. This lets you change the bind address without recompiling, and play nicely with containers.
use std::env;
async fn main_inner() {
let port = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
let addr = format!("0.0.0.0:{}", port);
println!("binding to {}", addr);
// bind and serve with addr ...
}Putting Setup Together
A complete startup wires it all: build the router with routes and shared state, read the port, bind a listener, and serve. With this skeleton in place, the next lessons add real endpoints, models, and persistence.
use axum::{routing::get, Router};
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() {
let db = Arc::new(Mutex::new(Vec::<String>::new()));
let app = Router::new()
.route("/health", get(|| async { "ok" }))
.with_state(db);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await.unwrap();
axum::serve(listener, app).await.unwrap();
}Quick Check
Test your understanding of project setup.
Recap
You set up a Rust REST API project:
- Use
cargo newand addaxum,tokio, andserde. #[tokio::main]provides the async runtime.- A
Routermaps paths and methods to async handlers. - Share data with
.with_stateand theStateextractor. - Split code into route, handler, and model modules; read the port from the environment.
Frequently asked questions
Is the “Project Setup” lesson free?
Yes — the full text of “Project Setup” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.
What will I learn in “Project Setup”?
Structure the API. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Learn Rust Coding?
No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Project Setup” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Learn Rust Coding lesson?
Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.