skills/axum-scaffolder/SKILL.md
Scaffolds Axum HTTP endpoints with OpenAPI documentation (utoipa), Tower middleware, JWT authentication, rate limiting, CORS, health checks, and versioning. Axum-first approach: typed extractors, typed responses, and compile-time route verification. Use when creating Rust REST APIs, scaffolding Axum endpoints, setting up Axum projects, configuring Tower middleware, adding OpenAPI documentation to Axum, or implementing JWT authentication in Axum. Triggers on: "scaffold axum", "create axum endpoint", "axum router", "add axum route", "rust rest api", "axum api", "axum openapi", "axum jwt", "axum middleware". Do NOT use when the existing codebase uses Actix-web — scaffolding Axum into an Actix project requires manual integration not covered here.
npx skillsauth add michaelalber/ai-toolkit axum-scaffolderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
"An API that is not documented is an API that is not finished." -- API Design Principle
"Security by default: every route is protected unless explicitly marked public." -- Axum Security Principle
Axum is Rust's most ergonomic HTTP framework, built on Tower's middleware ecosystem and Tokio's async runtime. This skill scaffolds production-ready Axum APIs with OpenAPI documentation, security middleware, and proper error handling from day one.
The key design decisions enforced by this skill:
#[utoipa::path] documentation. The OpenAPI spec is generated from code, not written separately./api/v1/. Adding v2 later is a router merge, not a refactor.Json<T>, Path<T>, Query<T>). Response data via impl IntoResponse. No raw Request parsing.AppError implementing IntoResponse. RFC 7807 Problem Details JSON format.Non-Negotiable Constraints:
#[utoipa::path] on every handler; utoipa::OpenApi derive on the app. The OpenAPI spec must be accurate./api/v1/ prefix on all routers. No unversioned routes./health, /docs).axum::extract::{Json, Path, Query, State}. No raw Request parsing in handlers.impl IntoResponse with explicit status codes. No magic number status codes in handlers.| # | Principle | Description | Applied As |
|---|-----------|-------------|------------|
| 1 | Router Organization | One Router per feature, merged at startup. Feature routers are defined in feature modules and merged in main.rs or app.rs. This keeps the main router clean and feature routers self-contained. | Define pub fn orders_router() -> Router<AppState> in each feature; merge with .nest("/api/v1/orders", orders_router()) in app startup. |
| 2 | Request/Response Types | Request types derive Deserialize and ToSchema. Response types derive Serialize and ToSchema. The ToSchema derive is required for OpenAPI documentation. | #[derive(Deserialize, ToSchema)] on request types; #[derive(Serialize, ToSchema)] on response types. |
| 3 | Validation | Input validation happens in the handler before calling the service. The validator crate provides derive-based validation. Validation failures return 422 Unprocessable Entity. | Use #[derive(Validate)] on request types; call req.validate()? in handlers; map ValidationError to AppError::Validation. |
| 4 | OpenAPI Documentation | Every handler has #[utoipa::path] with request body, response types, and status codes. The utoipa::OpenApi derive on the app struct aggregates all paths. | Use #[utoipa::path] on every handler; include all response types in components(schemas(...)). |
| 5 | Versioning Strategy | URL prefix versioning (/api/v1/, /api/v2/). Version is in the URL, not in headers. New versions are new routers merged alongside old ones. Old versions are deprecated with a Deprecation header. | All feature routers are nested under /api/v1/. When v2 is needed, create new feature routers and nest under /api/v2/. |
| 6 | Authorization | Tower middleware layer validates JWT and extracts claims. Claims are injected as Extension<Claims>. Handlers that need authorization extract Extension<Claims>. | Apply auth middleware at the router level; use Extension<Claims> in handlers that need the authenticated user. |
| 7 | Rate Limiting | tower-governor provides per-IP rate limiting. Applied as a Tower layer. Rate limits are configurable per route group. | Apply GovernorLayer to the router; configure limits per route group (stricter for auth endpoints). |
| 8 | Error Handling | Custom AppError enum implementing IntoResponse. RFC 7807 Problem Details JSON format. Errors include a type, title, status, and detail. | Define AppError with IntoResponse; return Result<impl IntoResponse, AppError> from all handlers. |
| 9 | CORS | tower-http::cors::CorsLayer with explicit allowed origins. No wildcard origins in production. Credentials mode requires explicit origin allowlist. | Configure CorsLayer with allow_origin(AllowOrigin::list([...])) for production. |
| 10 | Health Checks | /health route returns 200 OK with JSON body. Used by load balancers and Kubernetes liveness/readiness probes. No authentication required. | Add /health route to the app router (not nested under /api/v1/); return {"status": "ok", "version": "..."}. |
| Query | When to Call |
|-------|--------------|
| search_knowledge("Axum router handler extractor State Tower middleware") | During scaffold — verify Axum patterns |
| search_knowledge("utoipa OpenAPI derive macro Axum documentation") | During OpenAPI setup — verify utoipa patterns |
| search_knowledge("Axum JWT authentication Tower layer middleware") | During auth setup — verify JWT middleware patterns |
| search_knowledge("Axum error handling IntoResponse AppError") | During error type design — verify error patterns |
| search_knowledge("tower-http CORS rate limiting middleware") | During middleware setup — verify Tower middleware |
DETECT (before scaffolding)
[ ] Check for existing Cargo.toml (identify existing dependencies)
[ ] Check for existing AppState, AppError, main router
[ ] Identify Rust edition and Tokio version
[ ] Check for existing utoipa setup
|
v
CONFIGURE (Cargo.toml dependencies and project structure)
[ ] Add required dependencies to Cargo.toml
[ ] Create src/errors.rs (AppError)
[ ] Create src/state.rs (AppState)
[ ] Create src/openapi.rs (OpenApi derive)
|
v
SCAFFOLD (router + handlers + models)
[ ] Create feature router with typed handlers
[ ] Create request/response models with ToSchema
[ ] Add utoipa::path documentation to handlers
[ ] Create error type with From<FeatureError> for AppError
|
v
SECURE (auth middleware)
[ ] Add JWT validation middleware
[ ] Apply to protected route groups
[ ] Leave /health and /docs unprotected
|
v
DOCUMENT (utoipa OpenAPI)
[ ] Add #[utoipa::path] to all handlers
[ ] Add ToSchema to all request/response types
[ ] Configure OpenApi derive with all paths and schemas
[ ] Add /docs route serving the OpenAPI UI
|
v
VERIFY
[ ] cargo build (no errors)
[ ] cargo test (all tests pass)
[ ] cargo clippy -- -D warnings (no warnings)
[ ] curl /health returns 200
[ ] curl /docs returns OpenAPI UI
Exit criteria: All routes scaffolded, documented, secured, and verified.
<axum-scaffold-state>
phase: DETECT | CONFIGURE | SCAFFOLD | SECURE | DOCUMENT | VERIFY | COMPLETE
feature_name: [name]
edition: [2015 | 2018 | 2021]
tokio_version: [1.x | unknown]
utoipa_configured: true | false
auth_middleware: true | false
rate_limiting: true | false
cors_configured: true | false
health_check: true | false
openapi_ui: true | false
build_status: pass | fail | not-run
last_action: [description]
next_action: [description]
</axum-scaffold-state>
## Axum Scaffold: [project/feature name]
### Dependencies Added (Cargo.toml)
- [ ] axum [version]
- [ ] tokio [version]
- [ ] tower [version]
- [ ] tower-http [version]
- [ ] utoipa [version]
- [ ] utoipa-swagger-ui [version]
- [ ] serde [version]
- [ ] thiserror [version]
### Files Created
- [ ] src/errors.rs (AppError)
- [ ] src/state.rs (AppState)
- [ ] src/openapi.rs (OpenApi derive)
- [ ] src/features/[name]/router.rs
- [ ] src/features/[name]/models.rs
- [ ] src/features/[name]/errors.rs
### Verification
- [ ] cargo build passes
- [ ] GET /health returns 200
- [ ] GET /docs returns OpenAPI UI
- [ ] Protected routes return 401 without token
[dependencies]
axum = { version = "0.7", features = ["macros"] }
tokio = { version = "1", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "trace", "compression-gzip"] }
tower-governor = "0.3"
utoipa = { version = "4", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "6", features = ["axum"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
jsonwebtoken = "9"
validator = { version = "0.18", features = ["derive"] }
WRONG:
async fn create_order(
State(state): State<AppState>,
Json(req): Json<CreateOrderRequest>,
) -> Result<impl IntoResponse, AppError> { ... }
RIGHT:
#[utoipa::path(
post,
path = "/api/v1/orders",
request_body = CreateOrderRequest,
responses(
(status = 201, description = "Order created", body = OrderResponse),
(status = 400, description = "Invalid request"),
(status = 401, description = "Unauthorized"),
(status = 422, description = "Validation error"),
),
security(("bearer_auth" = [])),
tag = "orders"
)]
async fn create_order(
State(state): State<AppState>,
Extension(claims): Extension<Claims>,
Json(req): Json<CreateOrderRequest>,
) -> Result<(StatusCode, Json<OrderResponse>), AppError> { ... }
WRONG:
// Auth check inside the handler
async fn get_order(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<impl IntoResponse, AppError> {
let token = headers.get("Authorization").ok_or(AppError::Unauthorized)?;
// validate token...
}
RIGHT:
// Auth middleware applied at router level
pub fn orders_router() -> Router<AppState> {
Router::new()
.route("/:id", get(get_order))
.layer(middleware::from_fn(jwt_auth_middleware))
}
// <AI-Generated START>
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_type, title, detail) = match &self {
AppError::NotFound(msg) => (
StatusCode::NOT_FOUND,
"https://example.com/errors/not-found",
"Resource Not Found",
msg.as_str(),
),
AppError::Unauthorized => (
StatusCode::UNAUTHORIZED,
"https://example.com/errors/unauthorized",
"Unauthorized",
"Authentication required",
),
AppError::Validation(msg) => (
StatusCode::UNPROCESSABLE_ENTITY,
"https://example.com/errors/validation",
"Validation Error",
msg.as_str(),
),
AppError::Internal(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
"https://example.com/errors/internal",
"Internal Server Error",
"An unexpected error occurred",
),
};
let body = Json(serde_json::json!({
"type": error_type,
"title": title,
"status": status.as_u16(),
"detail": detail,
}));
(status, body).into_response()
}
}
// <AI-Generated END>
| # | Anti-Pattern | Why It Fails | Correct Approach |
|---|-------------|-------------|-----------------|
| 1 | Unversioned Routes | Adding versioning later requires changing all client URLs. | Start with /api/v1/ prefix on all routes. |
| 2 | Auth in Handlers | Per-handler auth checks are easy to forget. One missed check = security hole. | Apply auth middleware at the router level. |
| 3 | Wildcard CORS | allow_any_origin() allows any website to make requests to your API. | Use explicit origin allowlist in production. |
| 4 | No OpenAPI Documentation | Undocumented APIs are unusable by other teams and impossible to test with tools. | #[utoipa::path] on every handler from day one. |
| 5 | Raw Request Parsing | req.headers().get("X-Custom") in handlers bypasses Axum's type system. | Use typed extractors: Extension<T>, TypedHeader<T>. |
| 6 | Panic in Handlers | .unwrap() in a handler panics the request, potentially crashing the server. | Return Result<impl IntoResponse, AppError>. Use ?. |
| 7 | String Error Responses | (StatusCode::BAD_REQUEST, "something went wrong") is not machine-readable. | Use RFC 7807 Problem Details JSON format. |
| 8 | No Rate Limiting on Auth | Unlimited login attempts enable brute-force attacks. | Apply stricter rate limits to /auth/login and /auth/register. |
| 9 | Blocking in Handlers | std::thread::sleep() in a handler blocks the Tokio executor thread. | Use tokio::time::sleep() and tokio::task::spawn_blocking(). |
| 10 | Missing Health Check | Load balancers and Kubernetes need a health endpoint to determine service availability. | Add /health returning 200 OK with {"status": "ok"}. |
Symptoms: cargo build fails with utoipa derive errors
Recovery:
1. Check utoipa version compatibility with axum version
2. Verify all types used in #[utoipa::path] implement ToSchema
3. Check that response types are included in OpenApi components(schemas(...))
4. Common fix: add #[derive(ToSchema)] to all request/response types
5. Check utoipa documentation for the specific error message
Symptoms: All requests return 401 after adding auth middleware
Recovery:
1. Verify the JWT secret matches between token generation and validation
2. Check token expiration (exp claim)
3. Check the Authorization header format: "Bearer <token>"
4. Add logging to the middleware to see what is being rejected
5. Test with a freshly generated token
Symptoms: Middleware not applied, or applied in wrong order
Recovery:
1. Tower layers are applied in reverse order (last added = outermost)
2. Auth middleware should be inner (applied after CORS, before handlers)
3. Correct order: .layer(cors).layer(trace).layer(auth)
4. Verify with a request trace log
| Skill | Relationship |
|-------|-------------|
| rust-feature-slice | Provides the feature module organization. axum-scaffolder provides the HTTP infrastructure (middleware, OpenAPI, auth). Use both together. |
| rust-security-review | After scaffolding, run rust-security-review to verify auth middleware, CORS, and input validation are correct. |
| rust-architecture-checklist | After scaffolding, run rust-architecture-checklist to verify handler thinness, error handling, and trait design. |
| sqlx-migration-manager | When the scaffolded API requires database access, use sqlx-migration-manager for the migration lifecycle. |
| minimal-api-scaffolder | Parallel skill for .NET Minimal API. Same OpenAPI-first philosophy; different ecosystem. |
development
Federal / government security overlay applied ON TOP OF a base language security review (dotnet/python/php/rust/react). Language-agnostic: adds NIST SP 800-53 control mapping, FIPS 140-2/3 cryptographic compliance (with a per-language crypto table), CUI handling, EO 14028 supply-chain requirements, and DOE Order 205.1B, and emits POA&M-ready findings with FIPS 199 impact levels. Use for federal/DOE/DOD/national-laboratory systems. Triggers on "federal security review", "NIST compliance", "NIST 800-53", "FISMA", "CUI", "FIPS audit", "DOE security", "POA&M", "ATO review". Do NOT use alone — run the matching <lang>-security-review FIRST; this overlay maps and extends it.
tools
OWASP-based security review of React / TypeScript front-end applications. Detects the framework (Vite/CRA/Next), entry points, and data flows, scans against the OWASP Top 10 (2025) mapped to React client-side patterns (XSS via raw HTML, URL/protocol injection, secrets in the bundle, insecure token storage, dependency CVEs, missing CSP, open redirects), and produces a manager-friendly executive summary plus a graded technical findings table. Use to audit React code for vulnerabilities. Triggers on "react security review", "frontend security audit", "audit react for vulnerabilities", "owasp react", "react xss", "react security posture", "npm audit review". For federal / gov / DOE / NIST / FIPS / CUI context, run security-review-federal after this base review. Do NOT use to grade architecture/structure — use react-architecture-checklist.
tools
Analyzes legacy React codebases and produces actionable modernization plans. Primary migration paths include class components to function components + hooks, Create React App to Vite, React 16/17 to 18 to 19, JavaScript to TypeScript, Enzyme to React Testing Library, legacy Redux to Redux Toolkit / Zustand / Context, and deprecated lifecycle/API removal. Does NOT perform the migration — assesses, quantifies risk, and plans. Triggers on phrases like "modernize react", "class to hooks", "upgrade react", "migrate CRA to vite", "react legacy migration", "react 17 to 18", "react js to typescript", "react technical debt", "enzyme to RTL".
development
Scaffolds feature-based React / TypeScript architecture using feature folders, presentational + container components, custom hooks, a typed data layer, and structural CQRS (query hooks vs mutation hooks). React analog of dotnet-vertical-slice and python-feature-slice — no DI framework; uses props/context for dependency injection and a query cache for server state. Use when creating feature-based React projects, adding React features, organizing components by feature rather than by technical type, or scaffolding a feature's data layer. Triggers on phrases like "scaffold react feature", "create react slice", "react feature folder", "react vertical slice", "add react feature", "react feature architecture", "organize react by feature".