Install any skill in seconds. Free to start, no credit card required.
Get Started Free →You are an expert in Axum, the web framework built on top of Tokio and Tower by the Tokio team. You help developers build high-performance, type-safe APIs and web services using Axum's extractor-based handler system, middleware via Tower layers, WebSocket support, and compile-time route validation — achieving C-level performance with Rust's memory safety guarantees.
.claude/skills/terminalskills-axum/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 104% | 0% |
| case-20 | ✓→✗ | ▼ Worse | 203% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 107% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 75% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 55% | 0% |
You are an expert in Axum, the web framework built on top of Tokio and Tower by the Tokio team. You help developers build high-performance, type-safe APIs and web services using Axum's extractor-based handler system, middleware via Tower layers, WebSocket support, and compile-time route validation — achieving C-level performance with Rust's memory safety guarantees.
rust// src/main.rs — Axum API server use axum::{ Router, Json, Extension, extract::{Path, Query, State}, http::StatusCode, routing::{get, post, put, delete}, middleware, }; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use std::sync::Arc; use tower_http::cors::CorsLayer; use tower_http::trace::TraceLayer; #[derive(Clone)] struct AppState { db: PgPool, redis: redis::Client, } #[tokio::main] async fn main() { tracing_subscriber::init(); let db = PgPool::connect(&std::env::var("DATABASE_URL").unwrap()) .await.unwrap(); let state = AppState { db, redis: redis::Client::open("redis://127.0.0.1/").unwrap(), }; let app = Router::new() .route("/users", get(list_users).post(create_user)) .route("/users/{id}", get(get_user).put(update_user).delete(delete_user)) .route("/health", get(|| async { "OK" })) .layer(CorsLayer::permissive()) .layer(TraceLayer::new_for_http()) .with_state(state); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); }
rust// Extractors pull data from requests — type-safe at compile time #[derive(Deserialize)] struct CreateUserRequest { name: String, email: String, } #[derive(Serialize)] struct UserResponse { id: i64, name: String, email: String, created_at: chrono::NaiveDateTime, } // State, Path, Query, Json are all extractors async fn create_user( State(state): State<AppState>, // Application state Json(payload): Json<CreateUserRequest>, // Request body ) -> Result<(StatusCode, Json<UserResponse>), AppError> { let user = sqlx::query_as!( UserResponse, "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *", payload.name, payload.email, ) .fetch_one(&state.db) .await?; Ok((StatusCode::CREATED, Json(user))) } async fn get_user( State(state): State<AppState>, Path(id): Path<i64>, // URL path parameter ) -> Result<Json<UserResponse>, AppError> { let user = sqlx::query_as!(UserResponse, "SELECT * FROM users WHERE id = $1", id) .fetch_optional(&state.db) .await? .ok_or(AppError::NotFound)?; Ok(Json(user)) } #[derive(Deserialize)] struct ListParams { page: Option<u32>, per_page: Option<u32>, } async fn list_users( State(state): State<AppState>, Query(params): Query<ListParams>, // Query string parameters ) -> Result<Json<Vec<UserResponse>>, AppError> { let page = params.page.unwrap_or(1); let per_page = params.per_page.unwrap_or(20).min(100); let offset = ((page - 1) * per_page) as i64; let users = sqlx::query_as!( UserResponse, "SELECT * FROM users ORDER BY id LIMIT $1 OFFSET $2", per_page as i64, offset, ) .fetch_all(&state.db) .await?; Ok(Json(users)) }
rustuse axum::response::IntoResponse; enum AppError { NotFound, Database(sqlx::Error), Unauthorized, } impl IntoResponse for AppError { fn into_response(self) -> axum::response::Response { let (status, message) = match self { AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"), AppError::Database(e) => { tracing::error!("Database error: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") } AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"), }; (status, Json(serde_json::json!({ "error": message }))).into_response() } } impl From<sqlx::Error> for AppError { fn from(e: sqlx::Error) -> Self { AppError::Database(e) } }
rustuse axum::middleware::Next; use axum::http::Request; async fn auth_middleware( State(state): State<AppState>, mut req: Request<axum::body::Body>, next: Next, ) -> Result<impl IntoResponse, AppError> { let token = req.headers() .get("Authorization") .and_then(|v| v.to_str().ok()) .and_then(|v| v.strip_prefix("Bearer ")) .ok_or(AppError::Unauthorized)?; let user = validate_token(&state.db, token).await?; req.extensions_mut().insert(user); Ok(next.run(req).await) } // Apply to specific routes let protected = Router::new() .route("/profile", get(get_profile)) .layer(middleware::from_fn_with_state(state.clone(), auth_middleware));
toml# Cargo.toml [dependencies] axum = "0.8" tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] } tower-http = { version = "0.6", features = ["cors", "trace"] } tracing = "0.1" tracing-subscriber = "0.3"
query_as! macro; catches SQL errors before runtimeIntoResponse for error enums; consistent error responses across all handlerswith_state() for database pools, config, caches; cloned cheaply via Arc internallytokio::signal to handle SIGTERM; Axum drains connections before stoppingextract::ws::WebSocket; integrates with Tower middleware| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 17,012 | 15,481 | -9% | 1 | 1 | 0% | 4,030 | 5,558 | +38% | 0 | 0 | — |
case-02 | pass→pass | 11,172 | 11,680 | +5% | 1 | 1 | 0% | 2,068 | 4,280 | +107% | 0 | 0 | — |
case-03 | pass→pass | 12,441 | 10,498 | -16% | 1 | 1 | 0% | 2,261 | 3,959 | +75% | 0 | 0 | — |
case-04 | fail→pass | 7,814 | 3,836 | -51% | 1 | 1 | 0% | 1,234 | 2,517 | +104% | 0 | 0 | — |
case-05 | pass→pass | 9,779 | 5,379 | -45% | 1 | 1 | 0% | 1,811 | 2,809 | +55% | 0 | 0 | — |
case-06 | pass→pass | 9,123 | 4,686 | -49% | 1 | 1 | 0% | 1,653 | 2,597 | +57% | 0 | 0 | — |
case-07 | pass→pass | 8,675 | 6,967 | -20% | 1 | 1 | 0% | 1,569 | 3,204 | +104% | 0 | 0 | — |
case-08 | pass→pass | 5,910 | 6,790 | +15% | 1 | 1 | 0% | 1,316 | 3,147 | +139% | 0 | 0 | — |
case-09 | pass→pass | 12,919 | 7,644 | -41% | 1 | 1 | 0% | 2,081 | 3,399 | +63% | 0 | 0 | — |
case-10 | pass→pass | 12,386 | 11,357 | -8% | 1 | 1 | 0% | 2,545 | 4,203 | +65% | 0 | 0 | — |
case-11 | pass→pass | 12,484 | 13,684 | +10% | 1 | 1 | 0% | 2,801 | 5,006 | +79% | 0 | 0 | — |
case-12 | pass→pass | 11,040 | 6,338 | -43% | 1 | 1 | 0% | 2,183 | 3,191 | +46% | 0 | 0 | — |
case-13 | pass→pass | 8,431 | 9,144 | +8% | 1 | 1 | 0% | 1,850 | 3,856 | +108% | 0 | 0 | — |
case-14 | pass→pass | 7,491 | 4,881 | -35% | 1 | 1 | 0% | 1,636 | 2,866 | +75% | 0 | 0 | — |
case-15 | pass→pass | 12,402 | 13,477 | +9% | 1 | 1 | 0% | 2,447 | 4,605 | +88% | 0 | 0 | — |
case-16 | pass→pass | 11,786 | 10,506 | -11% | 1 | 1 | 0% | 2,462 | 4,177 | +70% | 0 | 0 | — |
case-17 | pass→pass | 2,263 | 3,122 | +38% | 1 | 1 | 0% | 411 | 2,389 | +481% | 0 | 0 | — |
case-18 | pass→pass | 4,255 | 2,861 | -33% | 1 | 1 | 0% | 823 | 2,415 | +193% | 0 | 0 | — |
case-19 | pass→pass | 9,195 | 10,086 | +10% | 1 | 1 | 0% | 1,846 | 3,912 | +112% | 0 | 0 | — |
case-20 | pass→fail | 6,304 | 6,618 | +5% | 1 | 1 | 0% | 1,074 | 3,254 | +203% | 0 | 0 | — |
case-21 | pass→pass | 12,850 | 11,511 | -10% | 1 | 1 | 0% | 2,558 | 4,381 | +71% | 0 | 0 | — |
case-22 | pass→pass | 12,127 | 9,294 | -23% | 1 | 1 | 0% | 2,398 | 4,101 | +71% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.