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:

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?

BenefitWhat it buys you
Faster buildsChange one crate, only that crate (and its dependents) recompile
Clear boundariesdb can't accidentally reach into HTTP concerns
ReuseA 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.