# Step 6 — Errors, Auth & JSON Contracts

Our handlers work on the happy path but `.unwrap()` on every query — meaning any database hiccup *crashes the request*. Let's fix that, then add authentication and lock down the JSON contract between backend and frontend.

## Errors as HTTP responses

Recall from Step 2 that errors are values (`Result<T, E>`) and that `IntoResponse` is "anything that can become a response." Combine them: define **one error type** for your API and teach it how to become an HTTP response.

```rust
use axum::{http::StatusCode, response::{IntoResponse, Response}, Json};
use serde_json::json;

// Our API's error type.
enum ApiError {
    NotFound,
    Database(sqlx::Error),
}

// Teach it to render as an HTTP response.
impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            ApiError::NotFound => (StatusCode::NOT_FOUND, "not found".to_string()),
            ApiError::Database(e) => {
                tracing::error!(?e, "database error");   // log the detail...
                (StatusCode::INTERNAL_SERVER_ERROR, "internal error".to_string())
                //                                   ...but don't leak it to the client
            }
        };
        (status, Json(json!({ "error": message }))).into_response()
    }
}

// Let `?` convert sqlx errors into ApiError automatically.
impl From<sqlx::Error> for ApiError {
    fn from(e: sqlx::Error) -> Self {
        ApiError::Database(e)
    }
}
```

Now handlers return `Result<Json<T>, ApiError>` and use `?` instead of `.unwrap()`:

```rust
async fn get_task(
    State(pool): State<PgPool>,
    Path(id): Path<i64>,
) -> Result<Json<Task>, ApiError> {
    let task = sqlx::query_as::<_, Task>("SELECT id, title, done, created_at FROM tasks WHERE id = $1")
        .bind(id)
        .fetch_optional(&pool)   // Option<Task> instead of crashing on 0 rows
        .await?                  // sqlx::Error → ApiError via `From`, bubbled up
        .ok_or(ApiError::NotFound)?;   // None → 404
    Ok(Json(task))
}
```

Read the tail as a sentence: *"fetch optionally; if the DB errored, bubble it up as a 500; if there was no row, return 404; otherwise here's the task."* That's the entire error-handling model of a Rust backend — no exceptions, no try/catch, just values flowing through `?`.

> This is exactly how iKanban's backend does it: a central error type implements `IntoResponse`, and domain-specific cases map to the right status code (404, 403, 409, …). Crates like [`thiserror`](https://github.com/dtolnay/thiserror) reduce the boilerplate of defining such enums.

## Authentication with middleware

Auth is a **middleware** concern — a layer every protected request passes through before reaching a handler. In Axum, middleware can read the request, reject it, or attach data for handlers downstream.

```rust
use axum::{
    extract::Request,
    http::StatusCode,
    middleware::{self, Next},
    response::Response,
};

async fn require_auth(req: Request, next: Next) -> Result<Response, StatusCode> {
    // Read the Authorization header.
    let token = req.headers()
        .get("Authorization")
        .and_then(|v| v.to_str().ok())
        .and_then(|v| v.strip_prefix("Bearer "));

    match token {
        Some(t) if verify(t) => Ok(next.run(req).await),  // valid → continue
        _ => Err(StatusCode::UNAUTHORIZED),                // invalid → 401
    }
}

fn verify(_token: &str) -> bool {
    // In production: validate a JWT's signature/issuer/expiry.
    true
}
```

Apply it to the routes that need protecting:

```rust
let app = Router::new()
    .route("/tasks", get(list_tasks).post(create_task))
    .route("/tasks/{id}", get(get_task))
    .layer(middleware::from_fn(require_auth))   // wraps the routes above
    .with_state(pool);
```

Layers wrap the router like an onion — the request passes through the outer layers first, then reaches the handler, then the response travels back out. **Order matters.**

> **A production gotcha from iKanban:** in Axum, an `Extension` layer that shares per-request data must be the *outermost* layer, or the request can reach a handler before the data is attached and return 500s. Real JWT validation typically checks a signature against a provider's public keys (iKanban uses AWS Cognito) and inserts a `RequestContext` describing the user, which handlers then extract.

## Locking down the JSON contract

Serde gives you precise control over the shape of your request and response bodies. The distinction between required and optional fields is enforced by the **type**:

```rust
use serde::{Serialize, Deserialize};

#[derive(Deserialize)]
struct NewTask {
    title: String,             // required — request fails to parse if missing
    #[serde(default)]
    done: bool,                // optional — defaults to false if absent
    #[serde(alias = "dueDate")]
    due_date: Option<String>,  // optional AND accepts an old field name (back-compat)
}
```

| Attribute | Effect |
|---|---|
| `Option<T>` field | Field may be absent; you get `None` |
| `#[serde(default)]` | Missing field uses the type's default instead of erroring |
| `#[serde(alias = "x")]` | Accept an alternate incoming key (great for renames) |
| `#[serde(rename = "x")]` | Change the key name in the JSON entirely |

### Keeping frontend types in sync

A subtle superpower of a Rust backend: tools like [`ts-rs`](https://github.com/Aleph-Alpha/ts-rs) **generate TypeScript type definitions from your Rust structs**. Add a derive, and your `Task` struct emits a matching `Task` interface for the frontend:

```rust
#[derive(Serialize, ts_rs::TS)]
#[ts(export)]
struct Task { id: i64, title: String, done: bool }
```

Change the Rust struct, regenerate, and the frontend's compiler immediately sees the change. That's a **compile-time contract spanning two languages** — it's why iKanban's React frontend and Rust backend never drift out of sync on data shapes.

## Where we are

Your API returns proper error responses, rejects unauthenticated requests, and has a precise, versioned JSON contract. All that's left is to ship it. Next: **Step 7 — Build, Run & Deploy**.
