# Step 4 — Your First Endpoint (Axum)

Now we make the project answer HTTP requests. Axum is a web framework built on Tokio and [Tower](https://github.com/tower-rs/tower) (a middleware ecosystem). Its whole model is: **a router maps paths to handler functions, and handlers are plain `async fn`s.**

## A minimal server

Replace `src/main.rs` with:

```rust
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    // 1. Build the router: map GET / to the `root` handler.
    let app = Router::new().route("/", get(root));

    // 2. Bind a TCP listener.
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    // 3. Serve.
    println!("listening on http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

async fn root() -> &'static str {
    "Tasks API is running"
}
```

Run it:

```bash
cargo run
# then, in another terminal:
curl http://localhost:3000
# → Tasks API is running
```

Three things to notice:

- **`#[tokio::main]`** is a macro that sets up the Tokio async runtime and lets `main` be `async`. Without it, you can't `.await` anything.
- **`get(root)`** wires the HTTP GET method to your function. There's `post`, `put`, `delete`, etc.
- **The handler returns `&'static str`** — a plain string — and Axum turns it into a `200 OK` response. That works because `&str` implements the `IntoResponse` trait from Step 2.

## Returning JSON

A real API returns JSON. Define a type, derive `Serialize`, and wrap it in `Json`:

```rust
use axum::{routing::get, Json, Router};
use serde::Serialize;

#[derive(Serialize)]
struct Task {
    id: i64,
    title: String,
    done: bool,
}

async fn list_tasks() -> Json<Vec<Task>> {
    let tasks = vec![
        Task { id: 1, title: "Learn Axum".into(), done: true },
        Task { id: 2, title: "Ship the API".into(), done: false },
    ];
    Json(tasks)
}
```

Add it to the router:

```rust
let app = Router::new()
    .route("/", get(root))
    .route("/tasks", get(list_tasks));
```

```bash
curl http://localhost:3000/tasks
# → [{"id":1,"title":"Learn Axum","done":true}, ...]
```

`#[derive(Serialize)]` told the compiler to generate the struct-to-JSON code. `Json(...)` sets the `Content-Type: application/json` header and serializes the body. Done.

## Extractors: reading input from the request

Handlers declare what they need as parameters, and Axum's **extractors** supply them. The three you'll use most:

```rust
use axum::extract::{Path, Query, Json};

// Path parameter: GET /tasks/{id}
async fn get_task(Path(id): Path<i64>) -> Json<Task> { /* ... */ }

// Query string: GET /tasks?done=true
#[derive(serde::Deserialize)]
struct Filter { done: Option<bool> }
async fn list_tasks(Query(filter): Query<Filter>) -> Json<Vec<Task>> { /* ... */ }

// JSON body: POST /tasks  { "title": "..." }
#[derive(serde::Deserialize)]
struct NewTask { title: String }
async fn create_task(Json(body): Json<NewTask>) -> Json<Task> { /* ... */ }
```

Register the path param with `{id}` syntax:

```rust
let app = Router::new()
    .route("/tasks", get(list_tasks).post(create_task))
    .route("/tasks/{id}", get(get_task));
```

> **Gotcha worth memorizing:** Axum 0.7+ uses **`{id}`**, not the older `:id`. The old syntax compiles but *panics at startup* when the router is built. iKanban's coding guidelines call this out explicitly because a bad deploy silently rolls back on it.

## Sharing state: the database pool (preview)

Handlers need access to shared things — most importantly the database pool. You attach it once with `.with_state(...)` and extract it with `State`:

```rust
use axum::extract::State;
use sqlx::PgPool;

async fn list_tasks(State(pool): State<PgPool>) -> Json<Vec<Task>> {
    // use `pool` to query — we'll fill this in next step
    Json(vec![])
}

// when building the app:
let app = Router::new()
    .route("/tasks", get(list_tasks))
    .with_state(pool);   // every handler can now extract State<PgPool>
```

`PgPool` is cheap to clone (it's an `Arc` inside), so Axum hands each request its own handle to the same underlying connections.

## Where we are

You have a running server with JSON endpoints and you know how to read path params, query strings, and request bodies. The handlers return fake data — time to make them real. Next: **Step 5 — Talking to Postgres with SQLx**.
