Step 3 — Project & Workspace Setup
Time to create the project. Rust's build tool, Cargo, handles compiling, dependencies, testing, and running — you'll use it for everything.
Create the project
cargo new tasks-api
cd tasks-api
You get:
tasks-api/
├── Cargo.toml # project manifest: name, version, dependencies
└── src/
└── main.rs # entry point
Cargo.toml is the heart of the project. It's where "what this backend can do" is declared.
Add the backend dependencies
Open Cargo.toml and add the crates our Tasks API needs. These are the same core libraries iKanban's backend uses:
[package]
name = "tasks-api"
version = "0.1.0"
edition = "2024"
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = [
"runtime-tokio-rustls",
"postgres",
"macros",
"chrono",
] }
chrono = { version = "0.4", features = ["serde"] }
A few things worth understanding, because they trip up newcomers:
features = [...]— Rust crates ship optional pieces behind feature flags so you only compile what you use.tokio's"full"pulls in the whole runtime;sqlx's"postgres"enables the Postgres driver and"macros"enables the compile-time-checked query macros (Step 5).edition = "2024"— the language edition. Editions let Rust evolve without breaking old code; always start new projects on the latest.
Fetch and compile the dependency tree once:
cargo build
Cargo writes a Cargo.lock pinning exact versions. Commit Cargo.lock for an application — it makes builds reproducible.
The command you'll use constantly
While developing, you rarely run the binary directly. Instead:
cargo check # type-check WITHOUT producing a binary — fast, your inner loop
cargo run # compile + run
cargo test # run tests
cargo clippy # lints that catch real bugs and non-idiomatic code
cargo fmt # auto-format to the community style
cargo check is your best friend: it runs the full compiler front-end (so it catches every type and borrow error) but skips code generation, so it's fast. Run it after every change.
When one crate isn't enough: workspaces
As a backend grows, you split it into multiple crates (compilation units) inside one workspace. This is exactly how iKanban's backend is organized — a top-level Cargo.toml lists members:
# Cargo.toml at the workspace root
[workspace]
resolver = "2"
members = [
"crates/api", # the Axum HTTP server
"crates/db", # database access + migrations
"crates/domain", # shared types and business logic
]
Why bother?
| Benefit | What it buys you |
|---|---|
| Faster builds | Change one crate, only that crate (and its dependents) recompile |
| Clear boundaries | db can't accidentally reach into HTTP concerns |
| Reuse | A CLI and the server can both depend on domain |
iKanban's real backend has a dozen crates (remote for the API, db for schema, executors for AI runners, and so on). You don't need that on day one — start with a single crate and split when a boundary becomes obvious.
Rule of thumb: reach for a workspace when compile times hurt or when two parts of the system have genuinely different responsibilities. Splitting too early adds ceremony; splitting too late slows every build.
Where we are
You have a compiling Cargo project with Axum, Tokio, Serde, and SQLx on hand, and you understand how it grows into a workspace. Next: Step 4 — Your First Endpoint (Axum), where this project starts answering HTTP requests.