Updated Aug 27, 2026

01. Project Setup

📋 Jump to Takeaways

🎁 A Rust web service in 2026 starts with three crates and one command. Less ceremony than you might expect.

Creating the Project

cargo new url-shortener
cd url-shortener

That creates src/main.rs and Cargo.toml. Everything starts here.

Adding Dependencies

Edit Cargo.toml:

[package]
name = "url-shortener"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
  • axum — the web framework built on tokio and tower
  • tokio — the async runtime; features = ["full"] enables everything
  • serde + serde_json — JSON serialization

Run cargo build to download and compile dependencies. First build takes a minute — subsequent builds are incremental and fast.

Project Structure

Start flat. Don't create ten modules on day one:

url-shortener/
├── Cargo.toml
├── Cargo.lock          ← commit this — it pins exact dependency versions
└── src/
    └── main.rs

We'll grow the structure as the project needs it. The rule: add a module when a file gets too long, not before.

The Entry Point

main.rs is the wiring layer. It starts the runtime, builds the router, and binds to a port. No business logic here:

use axum::{Router, routing::get};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/health", get(health));

    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

async fn health() -> &'static str {
    "ok"
}

#[tokio::main] starts the Tokio runtime and makes main async. axum::serve runs the server and blocks until it's stopped.

Run it:

cargo run
curl http://localhost:3000/health
# ok

Cargo.lock

Always commit Cargo.lock for applications (binaries). It pins the exact versions of every dependency so builds are reproducible. For libraries you publish to crates.io, don't commit it — consumers use their own resolution.

Key Takeaways

  • cargo new creates the project; Cargo.toml declares dependencies
  • axum, tokio, and serde are the three crates every Rust web service starts with
  • #[tokio::main] starts the async runtime
  • axum::serve takes a TcpListener and a Router and runs the server
  • Start flat — add modules when files get too long, not before
  • Commit Cargo.lock for binaries, not for libraries

🎁 A health endpoint returns a string. But real routes return JSON, accept path parameters, and read request bodies. How does Axum handle all of that without a macro in sight?

📝 Ready to test your knowledge?

Answer the quiz below to mark this lesson complete.

Spot something off? Report an issue
© 2026 ByteLearn.dev. Free courses for developers. · Privacy