Loading...
Loading...
Complete guide for Axum web framework including routing, extractors, middleware, state management, error handling, and production deployment
npx skill4agent add manutej/luxor-claude-marketplace axum-web-frameworkServiceRouterServiceuse axum::{Router, routing::get};
let app = Router::new()
.route("/", get(handler))
.route("/users/:id", get(get_user))
.route("/posts", get(list_posts).post(create_post));IntoResponse// Simple handler
async fn handler() -> &'static str {
"Hello, World!"
}
// Handler with path parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
format!("User ID: {}", user_id)
}
// Handler with multiple extractors
async fn create_user(
State(state): State<AppState>,
Json(payload): Json<CreateUser>,
) -> Result<Json<User>, StatusCode> {
// Implementation
}FromRequestFromRequestPartsStateIntoResponseString&'static strJson<T>Html<String>StatusCode(StatusCode, T)(Parts, T)ResponseResult<T, E>StateCloneArc#[derive(Clone)]
struct AppState {
api_key: String,
}
let app = Router::new()
.route("/", get(handler))
.with_state(AppState {
api_key: "secret".to_string(),
});#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
cache: Arc<RwLock<Cache>>,
}// Define separate state types for different router sections
let api_router: Router<ApiState> = Router::new()
.route("/api/data", get(api_handler));
let app_router: Router<AppState> = Router::new()
.route("/app", get(app_handler));
// Combine with final state
let app = Router::new()
.nest("/", app_router.with_state(app_state))
.nest("/", api_router.with_state(api_state));towertower-httpmiddleware::from_fnServiceLayer.layer()ServiceBuilderRouter::layerRouterLayer::layerIntoResponseasync fn handler() -> Result<Json<Data>, StatusCode> {
// Returns 200 OK or error status code
}enum AppError {
Database(sqlx::Error),
NotFound,
Unauthorized,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
// Convert to HTTP response
}
}use axum::error_handling::HandleErrorLayer;
let app = Router::new()
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);use axum::{
Router,
routing::{get, post, put, delete, patch},
};
let app = Router::new()
.route("/", get(root))
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(get_user).put(update_user).delete(delete_user))
.route("/posts/:id/comments", get(get_comments).post(add_comment));use axum::extract::Path;
use serde::Deserialize;
// Single parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
format!("User {}", user_id)
}
// Multiple parameters
#[derive(Deserialize)]
struct PostPath {
user_id: u32,
post_id: u32,
}
async fn get_post(Path(params): Path<PostPath>) -> String {
format!("User {} Post {}", params.user_id, params.post_id)
}
// Using tuple for multiple params
async fn get_comment(
Path((post_id, comment_id)): Path<(u32, u32)>
) -> String {
format!("Post {} Comment {}", post_id, comment_id)
}// Captures all remaining path
async fn handler(Path(path): Path<String>) -> String {
format!("Captured path: {}", path)
}
let app = Router::new()
.route("/{*key}", get(handler));
// GET /foo/bar/baz -> path = "foo/bar/baz"use axum::{Router, routing::get};
fn api_routes() -> Router {
Router::new()
.route("/users", get(list_users))
.route("/users/:id", get(get_user))
.route("/posts", get(list_posts))
}
fn admin_routes() -> Router {
Router::new()
.route("/dashboard", get(dashboard))
.route("/settings", get(settings))
}
let app = Router::new()
.nest("/api", api_routes())
.nest("/admin", admin_routes())
.route("/", get(root));use axum::{http::StatusCode, handler::Handler};
async fn fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "Not Found")
}
let app = Router::new()
.route("/", get(handler))
.fallback(fallback);async fn api_fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "API endpoint not found")
}
let api = Router::new()
.route("/users", get(list_users))
.fallback(api_fallback);
let app = Router::new()
.nest("/api", api)
.fallback(fallback); // Used for non-/api routesuse axum::routing::{get, post, MethodRouter};
// Multiple methods on one route
let app = Router::new()
.route("/users", get(list_users).post(create_user));
// Different handlers per method
let app = Router::new()
.route("/resource",
get(get_resource)
.post(create_resource)
.put(update_resource)
.delete(delete_resource)
.patch(patch_resource)
);use axum::extract::Path;
use serde::Deserialize;
// Simple extraction
async fn user_by_id(Path(id): Path<u32>) -> String {
format!("User {}", id)
}
// Complex extraction
#[derive(Deserialize)]
struct Params {
org: String,
repo: String,
issue: u32,
}
async fn github_issue(Path(params): Path<Params>) -> String {
format!("{}/{} issue #{}", params.org, params.repo, params.issue)
}
let app = Router::new()
.route("/users/:id", get(user_by_id))
.route("/repos/:org/:repo/issues/:issue", get(github_issue));use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
async fn list_users(Query(pagination): Query<Pagination>) -> String {
let page = pagination.page.unwrap_or(1);
let per_page = pagination.per_page.unwrap_or(20);
format!("Page {} with {} items", page, per_page)
}
// GET /users?page=2&per_page=50use axum::extract::Json;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
username: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u32,
username: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
let user = User {
id: 123,
username: payload.username,
email: payload.email,
};
Json(user)
}use axum::extract::rejection::JsonRejection;
use axum::http::StatusCode;
async fn create_user(
payload: Result<Json<CreateUser>, JsonRejection>
) -> Result<Json<User>, (StatusCode, String)> {
match payload {
Ok(Json(create_user)) => {
// Valid JSON
Ok(Json(User { /* ... */ }))
}
Err(JsonRejection::MissingJsonContentType(_)) => {
Err((
StatusCode::BAD_REQUEST,
"Missing `Content-Type: application/json`".to_string(),
))
}
Err(JsonRejection::JsonDataError(err)) => {
Err((
StatusCode::BAD_REQUEST,
format!("Invalid JSON: {}", err),
))
}
Err(JsonRejection::JsonSyntaxError(err)) => {
Err((
StatusCode::BAD_REQUEST,
format!("JSON syntax error: {}", err),
))
}
Err(_) => {
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Unknown error".to_string(),
))
}
}
}use axum::extract::State;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
api_key: String,
}
async fn handler(State(state): State<AppState>) -> String {
format!("API Key: {}", state.api_key)
}
let state = AppState {
db_pool: Arc::new(DatabasePool::new()),
api_key: "secret".to_string(),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);use axum::extract::Extension;
#[derive(Clone)]
struct CurrentUser {
id: u32,
username: String,
}
async fn handler(Extension(user): Extension<CurrentUser>) -> String {
format!("Hello, {}", user.username)
}
// Set by middleware:
async fn auth_middleware(
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let user = CurrentUser {
id: 1,
username: "alice".to_string(),
};
req.extensions_mut().insert(user);
Ok(next.run(req).await)
}use axum::extract::Form;
use serde::Deserialize;
#[derive(Deserialize)]
struct LoginForm {
username: String,
password: String,
}
async fn login(Form(form): Form<LoginForm>) -> String {
format!("Logging in user: {}", form.username)
}use axum::http::HeaderMap;
async fn handler(headers: HeaderMap) -> String {
let user_agent = headers
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
format!("User-Agent: {}", user_agent)
}FromRequestFromRequestPartsuse axum::{
extract::{FromRequest, Request},
response::{IntoResponse, Response},
http::StatusCode,
async_trait,
};
struct AuthenticatedUser {
id: u32,
username: String,
}
#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(
parts: &mut Parts,
state: &S,
) -> Result<Self, Self::Rejection> {
// Extract and validate auth token
let auth_header = parts
.headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.ok_or_else(|| {
(StatusCode::UNAUTHORIZED, "Missing authorization header")
.into_response()
})?;
// Validate token and return user
Ok(AuthenticatedUser {
id: 1,
username: "alice".to_string(),
})
}
}
async fn protected_route(user: AuthenticatedUser) -> String {
format!("Hello, {}", user.username)
}use axum::{Router, routing::get, middleware};
use tower_http::trace::TraceLayer;
// 1. Router-level
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
// 2. Route-level
let app = Router::new()
.route("/protected", get(handler))
.route_layer(middleware::from_fn(auth_middleware));
// 3. Method-level
let app = Router::new()
.route("/resource",
get(handler)
.route_layer(middleware::from_fn(read_only_auth))
.post(create_handler)
.route_layer(middleware::from_fn(write_auth))
);.layer()let app = Router::new()
.route("/", get(handler))
.layer(layer_three) // Executes third
.layer(layer_two) // Executes second
.layer(layer_one); // Executes firstServiceBuilderuse tower::ServiceBuilder;
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(layer_one) // Executes first
.layer(layer_two) // Executes second
.layer(layer_three) // Executes third
);use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());use tower_http::compression::CompressionLayer;
let app = Router::new()
.route("/", get(handler))
.layer(CompressionLayer::new());use tower_http::cors::{CorsLayer, Any};
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.route("/api/data", get(handler))
.layer(cors);use tower::timeout::TimeoutLayer;
use std::time::Duration;
let app = Router::new()
.route("/", get(handler))
.layer(TimeoutLayer::new(Duration::from_secs(30)));use axum::{
error_handling::HandleErrorLayer,
http::{StatusCode, Method, Uri},
BoxError,
};
use tower::ServiceBuilder;
use std::time::Duration;
async fn handle_timeout_error(
method: Method,
uri: Uri,
err: BoxError,
) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
format!("`{} {}` request timed out", method, uri),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("`{} {}` failed with {}", method, uri, err),
)
}
}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_timeout_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);use axum::{
middleware::{self, Next},
extract::Request,
response::Response,
http::StatusCode,
};
async fn auth_middleware(
req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get("authorization")
.and_then(|h| h.to_str().ok());
if let Some(auth_header) = auth_header {
if validate_token(auth_header).await {
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
let app = Router::new()
.route("/protected", get(handler))
.layer(middleware::from_fn(auth_middleware));use axum::extract::Extension;
#[derive(Clone)]
struct CurrentUser {
id: u32,
username: String,
}
async fn auth_middleware(
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get("authorization")
.and_then(|h| h.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
if let Some(user) = authorize_user(auth_header).await {
req.extensions_mut().insert(user);
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
async fn handler(Extension(user): Extension<CurrentUser>) -> String {
format!("Hello, {}", user.username)
}
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(auth_middleware));ServiceLayeruse tower::{Service, Layer};
use axum::{response::Response, extract::Request};
use std::task::{Context, Poll};
use futures_core::future::BoxFuture;
#[derive(Clone)]
struct MyLayer;
impl<S> Layer<S> for MyLayer {
type Service = MyMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
MyMiddleware { inner }
}
}
#[derive(Clone)]
struct MyMiddleware<S> {
inner: S,
}
impl<S> Service<Request> for MyMiddleware<S>
where
S: Service<Request, Response = Response> + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
// Process request
let future = self.inner.call(request);
Box::pin(async move {
let response: Response = future.await?;
// Process response
Ok(response)
})
}
}
let app = Router::new()
.route("/", get(handler))
.layer(MyLayer);use axum::extract::State;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: Arc<Database>,
}
#[derive(Clone)]
struct MyLayer {
state: AppState,
}
impl<S> Layer<S> for MyLayer {
type Service = MyService<S>;
fn layer(&self, inner: S) -> Self::Service {
MyService {
inner,
state: self.state.clone(),
}
}
}
#[derive(Clone)]
struct MyService<S> {
inner: S,
state: AppState,
}
impl<S> Service<Request> for MyService<S>
where
S: Service<Request>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
// Use self.state here
self.inner.call(req)
}
}
let state = AppState {
db: Arc::new(Database::new()),
};
let app = Router::new()
.route("/", get(handler))
.layer(MyLayer { state: state.clone() })
.with_state(state);use tower::Layer;
use axum::ServiceExt;
fn rewrite_request_uri(req: Request) -> Request {
// Modify request URI
req
}
let middleware = tower::util::MapRequestLayer::new(rewrite_request_uri);
let app = Router::new()
.route("/", get(handler));
// Apply layer around entire router
let app_with_middleware = middleware.layer(app);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app_with_middleware.into_make_service()).await?;use axum::extract::State;
#[derive(Clone)]
struct AppState {
config: Config,
api_key: String,
}
async fn handler(State(state): State<AppState>) -> String {
format!("Config: {:?}, Key: {}", state.config, state.api_key)
}
let state = AppState {
config: Config::default(),
api_key: "secret".to_string(),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);Arcuse std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
cache: Arc<RwLock<HashMap<String, String>>>,
config: Config, // Cheap to clone
}
async fn handler(State(state): State<AppState>) -> Result<String, StatusCode> {
// Access database pool
let conn = state.db_pool.get().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// Access cache (read)
let cache = state.cache.read().await;
let value = cache.get("key");
// Access cache (write)
drop(cache); // Release read lock
let mut cache = state.cache.write().await;
cache.insert("new_key".to_string(), "value".to_string());
Ok("Success".to_string())
}#[derive(Clone)]
struct ApiState {
api_key: String,
}
#[derive(Clone)]
struct AppState {
db: Arc<Database>,
}
fn api_routes() -> Router<ApiState> {
Router::new()
.route("/data", get(|State(state): State<ApiState>| async move {
format!("API Key: {}", state.api_key)
}))
}
fn app_routes() -> Router<AppState> {
Router::new()
.route("/users", get(|State(state): State<AppState>| async move {
"Users".to_string()
}))
}
let api_state = ApiState { api_key: "secret".to_string() };
let app_state = AppState { db: Arc::new(Database::new()) };
let app = Router::new()
.nest("/api", api_routes().with_state(api_state))
.nest("/app", app_routes().with_state(app_state));fn routes<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/health", get(|| async { "OK" }))
.route("/version", get(|| async { "1.0.0" }))
}
// Can be combined with any state type
let app = Router::new()
.merge(routes())
.route("/", get(handler))
.with_state(AppState { /* ... */ });#[derive(Clone)]
struct StateA {
data_a: String,
}
#[derive(Clone)]
struct StateB {
data_b: String,
}
let router_a: Router<StateA> = Router::new()
.route("/a", get(|State(s): State<StateA>| async move { s.data_a }));
// Provide StateA, next missing state is StateB
let router_b: Router<StateB> = router_a.with_state(StateA {
data_a: "A".to_string(),
});
// Add routes needing StateB
let router_b = router_b
.route("/b", get(|State(s): State<StateB>| async move { s.data_b }));
// Provide StateB, now we have Router<()>
let app: Router<()> = router_b.with_state(StateB {
data_b: "B".to_string(),
});Resultuse axum::http::StatusCode;
async fn handler() -> Result<String, StatusCode> {
let result = some_operation().await;
match result {
Ok(data) => Ok(format!("Success: {}", data)),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
}
}IntoResponseuse axum::{
response::{IntoResponse, Response},
http::StatusCode,
Json,
};
use serde::Serialize;
#[derive(Debug)]
enum AppError {
Database(sqlx::Error),
NotFound,
Unauthorized,
ValidationError(String),
}
#[derive(Serialize)]
struct ErrorResponse {
error: String,
message: String,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::Database(e) => {
tracing::error!("Database error: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
}
AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, &msg),
};
let body = Json(ErrorResponse {
error: status.to_string(),
message: error_message.to_string(),
});
(status, body).into_response()
}
}
// Use in handler
async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
let user = db.get_user(id).await.map_err(AppError::Database)?;
user.ok_or(AppError::NotFound).map(Json)
}use axum::extract::rejection::JsonRejection;
async fn create_user(
payload: Result<Json<CreateUser>, JsonRejection>,
) -> Result<Json<User>, AppError> {
let Json(create_user) = payload.map_err(|err| match err {
JsonRejection::MissingJsonContentType(_) => {
AppError::ValidationError("Content-Type must be application/json".to_string())
}
JsonRejection::JsonDataError(e) => {
AppError::ValidationError(format!("Invalid JSON: {}", e))
}
JsonRejection::JsonSyntaxError(e) => {
AppError::ValidationError(format!("JSON syntax error: {}", e))
}
_ => AppError::ValidationError("Invalid request body".to_string()),
})?;
// Process create_user
Ok(Json(user))
}use axum::{
extract::{FromRequest, Request},
response::{IntoResponse, Response},
async_trait,
};
struct ValidatedJson<T>(T);
#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
T: serde::de::DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(data) = Json::<T>::from_request(req, state)
.await
.map_err(|err| {
(
StatusCode::BAD_REQUEST,
format!("Invalid JSON: {}", err),
).into_response()
})?;
data.validate().map_err(|err| {
(
StatusCode::BAD_REQUEST,
format!("Validation error: {}", err),
).into_response()
})?;
Ok(ValidatedJson(data))
}
}
async fn handler(ValidatedJson(data): ValidatedJson<MyData>) -> String {
// data is validated
format!("Received: {:?}", data)
}use axum::error_handling::HandleErrorLayer;
use tower::ServiceBuilder;
async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
"Request took too long".to_string(),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled error: {}", err),
)
}
}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_timeout_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);use axum::error_handling::HandleError;
async fn fallible_operation() -> Result<(), anyhow::Error> {
// Operation that might fail
Ok(())
}
let fallible_service = tower::service_fn(|_req| async {
fallible_operation().await?;
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});
async fn handle_error(err: anyhow::Error) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)
}
let app = Router::new().route_service(
"/",
HandleError::new(fallible_service, handle_error),
);use axum::Json;
use serde::Serialize;
#[derive(Serialize)]
struct ApiResponse {
success: bool,
data: String,
}
async fn handler() -> Json<ApiResponse> {
Json(ApiResponse {
success: true,
data: "Hello".to_string(),
})
}use axum::response::Html;
async fn handler() -> Html<&'static str> {
Html("<h1>Hello, World!</h1>")
}
async fn dynamic_html(Path(name): Path<String>) -> Html<String> {
Html(format!("<h1>Hello, {}</h1>", name))
}use axum::http::StatusCode;
async fn handler() -> StatusCode {
StatusCode::NO_CONTENT
}
async fn with_body() -> (StatusCode, String) {
(StatusCode::CREATED, "Resource created".to_string())
}
async fn json_with_status() -> (StatusCode, Json<ApiResponse>) {
(StatusCode::CREATED, Json(ApiResponse { /* ... */ }))
}use axum::http::{HeaderMap, header};
async fn handler() -> (HeaderMap, String) {
let mut headers = HeaderMap::new();
headers.insert(header::CACHE_CONTROL, "max-age=3600".parse().unwrap());
headers.insert("X-Custom-Header", "value".parse().unwrap());
(headers, "Response body".to_string())
}use axum::{
response::Response,
http::{StatusCode, header},
body::Body,
};
async fn handler() -> Response {
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/json")
.header("X-Custom", "value")
.body(Body::from(r#"{"status":"ok"}"#))
.unwrap()
}use axum::response::sse::{Event, Sse};
use futures::stream::{self, Stream};
use std::convert::Infallible;
async fn sse_handler() -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
let stream = stream::iter(0..10).map(|i| {
Ok(Event::default().data(format!("Event {}", i)))
});
Sse::new(stream)
}use axum::{
response::{Response, IntoResponse},
http::{header, StatusCode},
body::Body,
};
use tokio::fs::File;
async fn download_file() -> Result<Response, StatusCode> {
let file = File::open("path/to/file.pdf")
.await
.map_err(|_| StatusCode::NOT_FOUND)?;
let body = Body::from_stream(tokio_util::io::ReaderStream::new(file));
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/pdf")
.header(
header::CONTENT_DISPOSITION,
"attachment; filename=\"file.pdf\"",
)
.body(body)
.unwrap())
}use sqlx::{PgPool, postgres::PgPoolOptions};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: PgPool,
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(&state.db)
.await
.map_err(AppError::Database)?
.ok_or(AppError::NotFound)?;
Ok(Json(user))
}
#[tokio::main]
async fn main() {
let db = PgPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL").unwrap())
.await
.unwrap();
let state = AppState { db };
let app = Router::new()
.route("/users/:id", get(get_user))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}use serde::Deserialize;
use config::{Config, ConfigError, Environment};
#[derive(Debug, Deserialize, Clone)]
struct Settings {
database_url: String,
redis_url: String,
jwt_secret: String,
port: u16,
}
impl Settings {
fn new() -> Result<Self, ConfigError> {
Config::builder()
.add_source(Environment::default())
.build()?
.try_deserialize()
}
}
#[derive(Clone)]
struct AppState {
settings: Settings,
db: PgPool,
}
#[tokio::main]
async fn main() {
let settings = Settings::new().expect("Failed to load configuration");
let db = PgPoolOptions::new()
.connect(&settings.database_url)
.await
.expect("Failed to connect to database");
let state = AppState {
settings: settings.clone(),
db,
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);
let addr = format!("0.0.0.0:{}", settings.port);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}use tracing::{info, error, debug, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
info!("Starting server");
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
info!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
#[instrument]
async fn handler(Path(id): Path<u32>) -> Result<String, AppError> {
debug!("Handling request for user {}", id);
match get_user_from_db(id).await {
Ok(user) => {
info!("Successfully retrieved user {}", id);
Ok(format!("User: {}", user))
}
Err(e) => {
error!("Failed to get user {}: {}", id, e);
Err(AppError::Database(e))
}
}
}use tokio::signal;
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
println!("Signal received, starting graceful shutdown");
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}use serde::Serialize;
#[derive(Serialize)]
struct HealthResponse {
status: String,
database: String,
cache: String,
}
async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
let db_status = match sqlx::query("SELECT 1").fetch_one(&state.db).await {
Ok(_) => "healthy",
Err(_) => "unhealthy",
};
let cache_status = match state.redis.ping().await {
Ok(_) => "healthy",
Err(_) => "unhealthy",
};
Json(HealthResponse {
status: if db_status == "healthy" && cache_status == "healthy" {
"healthy".to_string()
} else {
"degraded".to_string()
},
database: db_status.to_string(),
cache: cache_status.to_string(),
})
}
let app = Router::new()
.route("/health", get(health_check))
.route("/ready", get(readiness_check))
.with_state(state);use tower::limit::RateLimitLayer;
use std::time::Duration;
let app = Router::new()
.route("/api/data", get(handler))
.layer(RateLimitLayer::new(
100, // max requests
Duration::from_secs(60), // per minute
));use validator::{Validate, ValidationError};
#[derive(Debug, Deserialize, Validate)]
struct CreateUserRequest {
#[validate(length(min = 3, max = 50))]
username: String,
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
}
struct ValidatedJson<T>(T);
#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = (StatusCode, String);
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(data) = Json::<T>::from_request(req, state)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
data.validate()
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
Ok(ValidatedJson(data))
}
}
async fn create_user(
ValidatedJson(user): ValidatedJson<CreateUserRequest>,
) -> Result<Json<User>, AppError> {
// user is validated
Ok(Json(User { /* ... */ }))
}#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt;
#[tokio::test]
async fn test_handler() {
let app = Router::new().route("/", get(handler));
let response = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode, Method};
use tower::ServiceExt;
use serde_json::json;
#[tokio::test]
async fn test_create_user() {
let state = AppState::test();
let app = create_app(state);
let request_body = json!({
"username": "testuser",
"email": "test@example.com"
});
let response = app
.oneshot(
Request::builder()
.method(Method::POST)
.uri("/users")
.header("content-type", "application/json")
.body(Body::from(request_body.to_string()))
.unwrap()
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
}
}trait UserRepository {
async fn get(&self, id: u32) -> Result<User, AppError>;
async fn create(&self, user: CreateUser) -> Result<User, AppError>;
async fn update(&self, id: u32, user: UpdateUser) -> Result<User, AppError>;
async fn delete(&self, id: u32) -> Result<(), AppError>;
}
struct PostgresUserRepository {
pool: PgPool,
}
impl UserRepository for PostgresUserRepository {
async fn get(&self, id: u32) -> Result<User, AppError> {
// Implementation
}
}
#[derive(Clone)]
struct AppState {
user_repo: Arc<dyn UserRepository + Send + Sync>,
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<u32>,
) -> Result<Json<User>, AppError> {
let user = state.user_repo.get(id).await?;
Ok(Json(user))
}struct UserService {
repo: Arc<dyn UserRepository + Send + Sync>,
email_service: Arc<EmailService>,
}
impl UserService {
async fn create_user(&self, data: CreateUser) -> Result<User, AppError> {
let user = self.repo.create(data).await?;
self.email_service.send_welcome_email(&user).await?;
Ok(user)
}
}
#[derive(Clone)]
struct AppState {
user_service: Arc<UserService>,
}fn v1_routes() -> Router<AppState> {
Router::new()
.route("/users", get(v1::list_users))
.route("/users/:id", get(v1::get_user))
}
fn v2_routes() -> Router<AppState> {
Router::new()
.route("/users", get(v2::list_users))
.route("/users/:id", get(v2::get_user))
}
let app = Router::new()
.nest("/api/v1", v1_routes())
.nest("/api/v2", v2_routes())
.with_state(state);