rust-axum
Skill0 starsChanged 35 days agoMITView on GitHub
Use when building HTTP APIs with axum.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- adxptived/Rust-Skillstheir own version, first seen Oct 17, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- adxptived/Rust-Skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository15 other files it gives its agents.
- AGENTS.mdAGENTS.md
- rustoriaSkill
- rust-debuggingSkill
- rust-dependenciesSkill
- rust-fundamentalsSkill
- rust-clapSkill
- rust-dieselSkill
- rust-ratatuiSkill
- rust-rayonSkill
- rust-serdeSkill
- rust-tokioSkill
- rust-productionSkill
- rust-code-auditSkill
- rust-stdSkill
- rust-unsafe-checkerSkill
The file
Files
Axum — HTTP Framework for Rust
Overview
Axum is an HTTP routing and request-handling library focused on ergonomics and modularity. It uses tower::Service for
middleware, giving access to the entire tower ecosystem.
Installation
[dependencies]
axum = "0.8" # 0.8.9 as of late 2026
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = "0.7"
tracing = "0.1"
tracing-subscriber = "0.3"Core Concepts
1. Basic Server
use axum::{
http::StatusCode,
routing::{get, post},
Json, Router,
};
use serde::{Deserialize, Serialize};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let app = Router::new()
.route("/", get(root))
.route("/users", post(create_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn root() -> &'static str {
"Hello, World!"
}
#[derive(Deserialize)]
struct CreateUser {
username: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u64,
username: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> (StatusCode, Json<User>) {
let user = User {
id: 1337,
username: payload.username,
email: payload.email,
};
(StatusCode::CREATED, Json(user))
}2. Routing
axum 0.8 route syntax: path parameters use
{id}(and{*rest}for wildcards). The old:id/*restsyntax from 0.7 was removed — registering a route containing:panics at startup. Migrate:/users/:id→/users/{id}.
// Fragment: handler functions (get_index, get_users, ...) are defined elsewhere.
let app = Router::new()
.route("/", get(get_index))
.route("/users", get(get_users).post(create_user))
.route("/users/{id}", get(get_user).put(update_user).delete(delete_user))
.route("/users/{id}/posts", get(get_user_posts))
.nest("/api", api_routes())
.fallback(not_found);
// Nested routes
fn api_routes() -> Router {
Router::new()
.route("/health", get(health_check))
.route("/version", get(version))
}3. Path Parameters
use axum::extract::Path;
use axum::Json;
// Matches "/users/{id}"; `User` is defined in §1.
async fn get_user(Path(id): Path<u64>) -> Json<User> {
Json(User { id, username: "...".into(), email: "...".into() })
}
// Multiple params — matches "/users/{user_id}/posts/{post_id}"
async fn get_user_post(
Path((user_id, post_id)): Path<(u64, u64)>
) -> String {
format!("User {}, Post {}", user_id, post_id)
}4. Query Parameters
use axum::extract::Query;
use axum::Json;
use serde::Deserialize;
#[derive(Deserialize)]
struct Pagination {
page: Option<u64>,
per_page: Option<u64>,
}
async fn list_users(Query(pagination): Query<Pagination>) -> Json<Vec<User>> {
let page = pagination.page.unwrap_or(1);
let per_page = pagination.per_page.unwrap_or(20);
println!("page {page}, per_page {per_page}");
Json(vec![]) // replace with a real query
}5. Extractors
Extractors are types that implement FromRequest or FromRequestParts. They parse the request and provide typed data
to handlers.
// Signature overview — illustrative fragment; `Params`/`AppState` are your own types.
use axum::{
extract::{Path, Query, Request, State},
http::{HeaderMap, StatusCode},
response::IntoResponse,
Json,
};
async fn handler(
Json(body): Json<CreateUser>, // JSON body
Path(id): Path<u64>, // path param
Query(params): Query<Params>, // query string
headers: HeaderMap, // all headers
State(state): State<AppState>, // application state
request: Request, // full request (consuming extractor goes last)
) -> impl IntoResponse {
let _ = (body, id, params, headers, state, request);
StatusCode::OK
}Custom extractor:
use axum::{
extract::FromRequestParts,
http::{header, request::Parts, StatusCode},
};
struct AuthUser(User); // `User` from §1
impl<S> FromRequestParts<S> for AuthUser
where
S: Send + Sync,
{
type Rejection = StatusCode;
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
let auth_header = parts
.headers
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
// Demo-grade: replace with real verification (JWT, session lookup, ...)
match auth_header.strip_prefix("Bearer ") {
Some(token) if !token.is_empty() => {
let user = User {
id: 1,
username: "demo".into(),
email: "demo@example.com".into(),
};
Ok(AuthUser(user))
}
_ => Err(StatusCode::UNAUTHORIZED),
}
}
}6. State
use axum::{extract::State, routing::get, Router};
use std::sync::Arc;
struct AppState {
// e.g. a database pool, HTTP client, config
environment: String,
}
async fn get_users(State(state): State<Arc<AppState>>) -> String {
format!("running in {}", state.environment)
}
// Arc<AppState> is Clone, so it satisfies with_state's bound without
// deriving Clone on AppState itself.
fn app(state: Arc<AppState>) -> Router {
Router::new()
.route("/users", get(get_users))
.with_state(state)
}7. Error Handling
use axum::{
extract::Path,
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
#[derive(Debug)]
enum AppError {
NotFound,
Database(String), // message from your DB layer (diesel, sqlx, ...)
Validation(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
match self {
AppError::NotFound => StatusCode::NOT_FOUND.into_response(),
AppError::Database(e) => {
tracing::error!("database error: {e}");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
// Return the owned String — a &str borrowed from `msg` would not
// live long enough (E0597).
AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg).into_response(),
}
}
}
// Handlers return Result<_, AppError> and use ?
async fn get_user(Path(id): Path<u64>) -> Result<Json<User>, AppError> {
if id == 0 {
return Err(AppError::NotFound);
}
// A DB failure maps like: .map_err(|e| AppError::Database(e.to_string()))?
Ok(Json(User {
id,
username: "demo".into(),
email: "demo@example.com".into(),
}))
}8. Middleware
use axum::{
extract::Request,
http::{header, HeaderValue, Method, StatusCode},
middleware::{self, Next},
response::Response,
routing::get,
Router,
};
use tower_http::compression::CompressionLayer;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
// Security: CorsLayer::permissive() allows ANY origin — fine for local dev,
// unsafe in production. Restrict origins, methods, and headers instead:
let cors = CorsLayer::new()
.allow_origin(
"https://app.example.com"
.parse::<HeaderValue>()
.expect("valid origin"),
)
.allow_methods([Method::GET, Method::POST])
.allow_headers([header::CONTENT_TYPE]);
let app = Router::new()
.route("/", get(root))
.layer(TraceLayer::new_for_http())
.layer(cors)
.layer(CompressionLayer::new());
// Custom middleware
async fn auth_middleware(
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
let token = request
.headers()
.get(header::AUTHORIZATION)
.ok_or(StatusCode::UNAUTHORIZED)?;
println!("token present: {}", !token.is_empty());
Ok(next.run(request).await)
}
let protected = Router::new()
.route("/protected", get(root))
.route_layer(middleware::from_fn(auth_middleware));9. JSON API
use axum::Json;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct ApiResponse<T> {
data: T,
message: Option<String>,
}
async fn get_users() -> Json<ApiResponse<Vec<User>>> {
Json(ApiResponse {
data: vec![], // replace with a real query
message: None,
})
}10. WebSocket
Requires the ws feature: axum = { version = "0.8", features = ["ws"] }.
use axum::{
extract::ws::{WebSocket, WebSocketUpgrade},
response::IntoResponse,
};
async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(handle_socket)
}
async fn handle_socket(mut socket: WebSocket) {
while let Some(msg) = socket.recv().await {
let msg = if let Ok(msg) = msg { msg } else { return; };
if socket.send(msg).await.is_err() { return; }
}
}11. Testing
ServiceExt::oneshot comes from tower — for axum 0.8 add it as a dev-dependency:
[dev-dependencies]
tower = { version = "0.5", features = ["util"] }use axum::{
body::Body,
http::{Request, StatusCode},
routing::get,
Router,
};
use tower::ServiceExt; // for oneshot
#[tokio::test]
async fn test_get_user() {
// `get_user` from §7
let app = Router::new().route("/users/{id}", get(get_user));
let response = app
.oneshot(Request::builder().uri("/users/1").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}Alternative: bind a real TcpListener on port 0 and test over HTTP with reqwest — slower, but exercises the full stack.
12. Best Practices
- Use
Statefor shared dependencies — don't use global statics - Implement
IntoResponsefor errors — clean error handling - Use
tower-httpmiddleware — CORS, tracing, compression - Validate input with extractors — don't trust raw input
- Use
tracingfor logging — structured, async-aware - Keep handlers thin — business logic in services
- Use
Router::nestfor API versioning - Return proper status codes — 201 for create, 204 for delete
When to Use Axum
- REST APIs
- Microservices
- WebSocket servers
- Proxy servers
- Any HTTP-based service
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

