> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/tokio-rs/axum/llms.txt
> Use this file to discover all available pages before exploring further.

# Responses

> Learn how to return different types of responses in Axum

In Axum, any type that implements the `IntoResponse` trait can be returned from a handler. This provides a flexible and type-safe way to construct HTTP responses.

## The IntoResponse trait

The `IntoResponse` trait converts types into HTTP responses:

```rust theme={null}
use axum::response::{IntoResponse, Response};

pub trait IntoResponse {
    fn into_response(self) -> Response;
}
```

Many types implement this trait out of the box.

## Basic response types

<Tabs>
  <Tab title="Strings">
    String types return plain text responses:

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

    async fn handler() -> &'static str {
        "Hello, World!"
    }

    async fn dynamic() -> String {
        format!("The time is: {:?}", std::time::SystemTime::now())
    }

    let app = Router::new()
        .route("/static", get(handler))
        .route("/dynamic", get(dynamic));
    ```

    Returns `Content-Type: text/plain; charset=utf-8`
  </Tab>

  <Tab title="Status codes">
    Return HTTP status codes:

    ```rust theme={null}
    use axum::{
        Router,
        routing::{get, delete},
        http::StatusCode,
    };

    async fn health() -> StatusCode {
        StatusCode::OK
    }

    async fn delete_user() -> StatusCode {
        // Delete user logic...
        StatusCode::NO_CONTENT
    }

    let app = Router::new()
        .route("/health", get(health))
        .route("/users/:id", delete(delete_user));
    ```
  </Tab>

  <Tab title="Empty responses">
    Unit type returns empty 200 OK:

    ```rust theme={null}
    use axum::{Router, routing::post, http::StatusCode};
    use axum::response::NoContent;

    // Returns 200 OK with empty body
    async fn handler_ok() {}

    // Returns 204 No Content explicitly
    async fn handler_no_content() -> NoContent {
        NoContent
    }

    let app = Router::new()
        .route("/ok", post(handler_ok))
        .route("/no-content", post(handler_no_content));
    ```
  </Tab>

  <Tab title="HTML">
    Return HTML content:

    ```rust theme={null}
    use axum::{Router, routing::get, response::Html};

    async fn handler() -> Html<&'static str> {
        Html("<h1>Hello, World!</h1>")
    }

    async fn dynamic_html() -> Html<String> {
        Html(format!("<p>Generated at {:?}</p>", std::time::SystemTime::now()))
    }

    let app = Router::new()
        .route("/", get(handler))
        .route("/dynamic", get(dynamic_html));
    ```

    Returns `Content-Type: text/html; charset=utf-8`
  </Tab>
</Tabs>

## JSON responses

Return JSON using the `Json` wrapper:

```rust theme={null}
use axum::{Router, routing::get, Json};
use serde::{Serialize, Deserialize};

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
}

async fn get_user() -> Json<User> {
    Json(User {
        id: 1,
        name: "Alice".to_string(),
    })
}

let app = Router::new()
    .route("/users/1", get(get_user));
```

Returns `Content-Type: application/json`

## Tuple responses

Combine status codes, headers, and bodies using tuples:

```rust theme={null}
use axum::{
    Router,
    routing::{get, post},
    http::{StatusCode, HeaderMap, header},
    Json,
};
use serde::Serialize;

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
}

// Status + Body
async fn create_user() -> (StatusCode, Json<User>) {
    (
        StatusCode::CREATED,
        Json(User { id: 1, name: "Alice".to_string() })
    )
}

// Status + Headers + Body
async fn handler_with_headers() -> (StatusCode, HeaderMap, &'static str) {
    let mut headers = HeaderMap::new();
    headers.insert(header::CACHE_CONTROL, "max-age=3600".parse().unwrap());
    
    (StatusCode::OK, headers, "Cached response")
}

// Headers + Body (status defaults to 200)
async fn with_headers() -> (HeaderMap, &'static str) {
    let mut headers = HeaderMap::new();
    headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
    
    (headers, "Hello")
}

let app = Router::new()
    .route("/users", post(create_user))
    .route("/cached", get(handler_with_headers))
    .route("/headers", get(with_headers));
```

## Custom headers

Add headers to responses:

<CodeGroup>
  ```rust Using tuples theme={null}
  use axum::{
      http::{StatusCode, header},
      response::IntoResponse,
  };

  async fn handler() -> impl IntoResponse {
      (
          StatusCode::OK,
          [(header::CONTENT_TYPE, "text/plain")],
          "Hello, World!"
      )
  }
  ```

  ```rust Using AppendHeaders theme={null}
  use axum::{
      response::{IntoResponse, AppendHeaders},
      http::header,
  };

  async fn handler() -> impl IntoResponse {
      (
          AppendHeaders([
              (header::CACHE_CONTROL, "max-age=3600"),
              (header::CONTENT_TYPE, "text/plain"),
          ]),
          "Hello, World!"
      )
  }
  ```
</CodeGroup>

## Redirects

Redirect to different URLs:

```rust theme={null}
use axum::{
    Router,
    routing::get,
    response::Redirect,
};

// Temporary redirect (302)
async fn old_route() -> Redirect {
    Redirect::temporary("/new-route")
}

// Permanent redirect (301)
async fn moved() -> Redirect {
    Redirect::permanent("/new-location")
}

// See Other redirect (303)
async fn after_post() -> Redirect {
    Redirect::to("/success")
}

let app = Router::new()
    .route("/old", get(old_route))
    .route("/moved", get(moved))
    .route("/redirect", get(after_post));
```

## Result types

Use `Result` for error handling:

```rust theme={null}
use axum::{
    Router,
    routing::get,
    http::StatusCode,
    Json,
};
use serde::Serialize;

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
}

async fn get_user() -> Result<Json<User>, StatusCode> {
    let user = fetch_user_from_db().await;
    
    match user {
        Some(user) => Ok(Json(user)),
        None => Err(StatusCode::NOT_FOUND),
    }
}

async fn fetch_user_from_db() -> Option<User> {
    Some(User { id: 1, name: "Alice".to_string() })
}

let app = Router::new()
    .route("/users/:id", get(get_user));
```

<Note>
  Both the `Ok` and `Err` types must implement `IntoResponse`.
</Note>

## Custom response types

Implement `IntoResponse` for your own types:

```rust theme={null}
use axum::{
    response::{IntoResponse, Response},
    http::StatusCode,
    Json,
};
use serde::Serialize;

#[derive(Serialize)]
struct ErrorResponse {
    message: String,
    code: u16,
}

enum AppError {
    NotFound,
    Unauthorized,
    InternalError,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::NotFound => (
                StatusCode::NOT_FOUND,
                "Resource not found",
            ),
            AppError::Unauthorized => (
                StatusCode::UNAUTHORIZED,
                "Unauthorized access",
            ),
            AppError::InternalError => (
                StatusCode::INTERNAL_SERVER_ERROR,
                "Internal server error",
            ),
        };

        let body = Json(ErrorResponse {
            message: message.to_string(),
            code: status.as_u16(),
        });

        (status, body).into_response()
    }
}

// Use in handlers
async fn handler() -> Result<&'static str, AppError> {
    if !is_authorized() {
        return Err(AppError::Unauthorized);
    }
    Ok("Success")
}

fn is_authorized() -> bool { true }
```

## Response builders

For full control, build responses manually:

```rust theme={null}
use axum::{
    response::{IntoResponse, Response},
    http::{StatusCode, header},
    body::Body,
};

async fn custom_response() -> Response {
    Response::builder()
        .status(StatusCode::OK)
        .header(header::CONTENT_TYPE, "text/html")
        .header(header::CACHE_CONTROL, "max-age=3600")
        .body(Body::from("<h1>Custom Response</h1>"))
        .unwrap()
}
```

## Streaming responses

<Accordion title="Server-Sent Events (SSE)">
  Stream events to clients:

  ```rust theme={null}
  use axum::{
      Router,
      routing::get,
      response::sse::{Event, Sse},
  };
  use futures::stream::{self, Stream};
  use std::{convert::Infallible, time::Duration};
  use tokio_stream::StreamExt as _;

  async fn sse_handler() -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
      let stream = stream::repeat_with(|| Event::default().data("hi"))
          .map(Ok)
          .throttle(Duration::from_secs(1));

      Sse::new(stream)
  }

  let app = Router::new()
      .route("/events", get(sse_handler));
  ```
</Accordion>

## Response parts

Modify response parts separately:

```rust theme={null}
use axum::{
    response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
    http::StatusCode,
};

struct CustomHeader;

impl IntoResponseParts for CustomHeader {
    type Error = (StatusCode, String);

    fn into_response_parts(
        self,
        mut res: ResponseParts
    ) -> Result<ResponseParts, Self::Error> {
        res.headers_mut().insert(
            "x-custom-header",
            "custom-value".parse().unwrap()
        );
        Ok(res)
    }
}

async fn handler() -> impl IntoResponse {
    (CustomHeader, "Response body")
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Error Handling" icon="triangle-exclamation" href="/concepts/error-handling">
    Learn advanced error handling patterns
  </Card>

  <Card title="Middleware" icon="layer-group" href="/concepts/middleware">
    Transform requests and responses with middleware
  </Card>
</CardGroup>
