Przejdź do treści
Biblioteka skilli
Programowanie · Budowanie i kod

axum-patterns

/axum-patterns

Pomaga pisać i przeglądać trasy, ekstraktory i middleware we frameworku axum 0.8 dla Rust.

Repozytorium
agents ↗
Licencja
MIT
Ostatnia zmiana repozytorium
09.11.2026

Ocena i bezpieczeństwo

100/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść35 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
3
Skrypty
0
Komentarze HTML
0
Adresy URL w SKILL.md
0
Przestarzałość

Brak sygnałów. Repozytorium zmienione 09.11.2026, bez odwołań do wycofanych modeli i API.

Licencja

MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.

Co robi i kiedy

Pomaga pisać i przeglądać trasy, ekstraktory i middleware we frameworku axum 0.8 dla Rust.

Oryginalny opis (EN)

axum 0.8 framework patterns for this backend — routers, state, extractors, responses, middleware, and tower integration. Use when building or reviewing axum routes, handlers, extractors, `IntoResponse` impls, or middleware. Framework-focused; HTTP contract design lives in api-design and DB access in rust-backend-design.

Jak działa

  1. When to activate
  2. Router structure
  3. State
  4. Extractors
  5. Responses
  6. Error handling
  7. Middleware
  8. Validation with garde
  9. Tower / tower-http layers
  10. Multipart uploads
Sekcje z nagłówków SKILL.md, w oryginale · 280 linii, 1 155 słów
Podgląd SKILL.md
---
name: axum-patterns
description: axum 0.8 framework patterns for this backend — routers, state, extractors, responses, middleware, and tower integration. Use when building or reviewing axum routes, handlers, extractors, `IntoResponse` impls, or middleware. Framework-focused; HTTP contract design lives in api-design and DB access in rust-backend-design.
license: MIT
metadata:
  author: rainingdaemons/agents
  version: "1.0"
---

# axum Patterns (axum 0.8)

Framework-level patterns for this backend. This skill owns **how to wire axum**: routers, state, extractors, responses, and middleware. It does not cover REST contract design (`api-design`), database access (`rust-backend-design`), or Rust language mechanics (`rust-patterns`).

## When to activate

- Defining or restructuring routers and route handlers
- Writing custom extractors or `IntoResponse` implementations
- Adding middleware with tower `Layer` or `axum::middleware::from_fn`
- Handling multipart uploads (`axum-extra`) or typed path/query params

## Router structure

Routers are typed by the state they carry. Split public vs. protected routes and merge them:

```rust
use axum::{Router, routing::{delete, get, patch, post}, extract::{Json, Path, Query, State}, http::StatusCode};
use crate::common::middleware::{AuthLayer, RequireRoleLayer, Role};
use crate::connection::ConnectionPool;

pub fn products_router(jwt_secret: Arc<str>) -> Router<ConnectionPool> {
    let public = Router::new()
        .route("/", get(get_all_products))
        .route("/{id}", get(get_product_by_id));

    // `.layer` applies to every route declared before it; order is bottom-up.
    let protected = Router::new()
        .route("/", post(create_product))
        .route("/{id}", delete(delete_product))
        .layer(RequireRoleLayer::new(Role::Admin))
        .layer(AuthLayer::new(jwt_secret));

    public.merge(protected)
}
```

- Path params use axum 0.8 syntax `/{id}` (not `/:id`).
- `nest("/prefix", router)` mounts a sub-router; `merge` combines two routers at the same base.
- Attach a `Layer` **after** the routes it should cover.

## State

State is cloned per request; use an `Arc`-based type or the `ConnectionPool` directly (it is internally reference-counted):

```rust
async fn get_all_products(
    State(pool): State<ConnectionPool>,
    Query(params): Query<ProductsParams>,
) -> Result<Json<ProductsListResponse>, (StatusCode, String)> {
    // ...
}
```

Install it once with `.with_state(pool)` when building the root router.

## Extractors

- `Path<T>`, `Query<T>`, `Json<T>`, `State<T>`, `Extension<T>` implement `FromRequest`/`FromRequestParts`.
- A handler may take **at most one** `FromRequest` extractor (the body consumer) and it must be **last**:

```rust
async fn patch_product(
    State(pool): State<ConnectionPool>,
    Path(id): Path<Uuid>,
    Json(payload): Json<UpdateProductRequest>, // body consumer — last
) -> Result<Json<MessageResponse>, (StatusCode, String)> { /* ... */ }
```

The repo reads middleware-inserted values with `Extension<AuthUser>` today:

```rust
async fn get_profile(
    State(pool): State<ConnectionPool>,
    Extension(user): Extension<AuthUser>,
) -> Result<Json<DataResponse<Profile>>, (StatusCode, String)> { /* ... */ }
```

If you want authentication to fail earlier (before the body), you can add a `FromRequestParts` impl instead — but note `Extension<AuthUser>` only works because `AuthLayer` always runs first:

```rust
#[async_trait]
impl<S> FromRequestParts<S> for AuthUser
where S: Send + Sync {
    type Rejection = (StatusCode, String);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        parts.extensions.get::<AuthUser>().cloned()
            .ok_or((StatusCode::UNAUTHORIZED, "Not authenticated".to_string()))
    }
}
```

## Responses

Anything implementing `IntoResponse` can be returned. Common forms:

```rust
Json(value)                                  // 200 + JSON body
(StatusCode::CREATED, Json(value))           // tuple sets status
(StatusCode::NOT_FOUND, "not found")         // Optional<_> not needed: use Result
```

The repo's response envelope is the tuple structs in `routers/categories.rs`:

```rust
#[derive(Serialize)] pub struct MessageResponse { pub mensaje: String }
#[derive(Serialize)] pub struct DataResponse<T: Serialize> { pub datos: T }
```

`api-design` owns the target envelope (`{ "data": ... }` / `{ "error": { "code", "message" } }`); see `architecture.md` §5 for the convergence plan.

## Error handling

Two rejection shapes exist today — match the file you are editing:

```rust
// (StatusCode, String) — categories.rs, products.rs, variants.rs, variations.rs
pub fn internal_error<E: std::error::Error>(err: E) -> (StatusCode, String) {
    (StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}

// (StatusCode, Json<ErrorResponse>) — auth.rs, account.rs, orders.rs, users.rs, reports.rs, directions.rs
#[derive(Serialize)] struct ErrorResponse { error: String }
fn internal_error<E: std::error::Error>(err: E) -> (StatusCode, Json<ErrorResponse>) {
    (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: err.to_string() }))
}
```

- Validation failures → `(StatusCode::BAD_REQUEST, map_garde_errors(report))`.
- Not found → `(StatusCode::NOT_FOUND, "…".to_string())`.

Both current helpers put the raw error string in the response; `architecture.md` says not to leak internals. Target: `tracing::error!` the real error and return a generic message. Don't widen the leak — fix it when you touch the handler.

## Middleware

Two ways, choose by complexity:

```rust
// Option A — impl Layer + Service (stateful, reusable; used by AuthLayer / RequireRoleLayer)
impl<S> Layer<S> for AuthLayer { /* ... */ }

// Option B — from_fn (simple, per-route logic)
async fn require_admin(
    user: Option<Extension<AuthUser>>,
    request: Request,
    next: Next,
) -> Result<Response, (StatusCode, String)> {
    match user {
        Some(Extension(u)) if u.role >= Role::Admin => Ok(next.run(request).await),
        Some(_) => Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string())),
        None    => Err((StatusCode::UNAUTHORIZED, "Not authenticated".to_string())),
    }
}

let app = Router::new()
    .route("/api/admin/stats", get(stats))
    .layer(axum::middleware::from_fn(require_admin))
    .layer(AuthLayer::new(secret));
```

Implementing `Layer`/`Service` by hand (as the repo does) is verbose; prefer the `tower` ecosystem layers when possible. See `common/middleware.rs` for the canonical `AuthLayer` + `RequireRoleLayer`.

## Validation with garde

Validate request DTOs before touching the DB:

```rust
#[derive(Validate, Deserialize)]
struct CreateCategoryRequest {
    #[garde(length(min = 1, max = 255))] name: String,
    #[garde(skip)] description: Option<String>,
}

if let Err(report) = payload.validate() {
    return Err((StatusCode::BAD_REQUEST, map_garde_errors(report)));
}
```

## Tower / tower-http layers

Reuse the installed stack instead of hand-rolling: `tower-http::cors`, `tower::timeout::TimeoutLayer`, `tower::retry::RetryLayer`, `tower::limit::{RateLimitLayer, ConcurrencyLimitLayer}` (the worker uses the last two).

## Multipart uploads

`axum-extra` provides `Multipart` (enabled in `Cargo.toml`). Stream fields; enforce size limits before writing to disk (`routers/images.rs`).

## Examples from the field

Generic patterns lifted from `references/axum_middleware.md` and `references/axum_framework_guide.md`.

Auth middleware that bails early with `?` (idiomatic `Result<Response, StatusCode>`):

```rust
use axum::{
    extract::Request,
    http::{StatusCode, header},
    middleware::Next,
    response::Response,
};

async fn require_auth(req: Request, next: Next) -> Result<Response, StatusCode> {
    let token = req
        .headers()
        .get(header::AUTHORIZATION)          // typed constant, not "authorization"
        .and_then(|v| v.to_str().ok())
        .ok_or(StatusCode::UNAUTHORIZED)?;   // bail out before the handler runs
    if token == expected { Ok(next.run(req).await) } else { Err(StatusCode::UNAUTHORIZED) }
}
```

`.layer()` wraps everything including the 404 fallback; `.route_layer()` wraps only defined routes. Prefer `.route_layer()` for auth:

```rust
let app = Router::new()
    .route("/private", get(private_handler))
    .route_layer(from_fn(require_auth));     // 404s never hit auth
```

Layer order is an onion — **the last `.layer()` written is the outermost and runs first**:

```rust
let app = Router::new()
    .route("/dashboard", get(dashboard))
    .route_layer(from_fn(require_auth))  // runs 2nd
    .layer(from_fn(logger));             // runs 1st (outermost = listed last)
```

Custom error type instead of tuple rejections (`?` works in the handler):

```rust
#[derive(Debug)]
enum ApiError { NotFound, Internal(anyhow::Error) }

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let status = match self { ApiError::NotFound => StatusCode::NOT_FOUND, _ => StatusCode::INTERNAL_SERVER_ERROR };
        (status, Json(serde_json::json!({ "error": status.as_str() }))).into_response()
    }
}

async fn handler() -> Result<Json<Item>, ApiError> { /* ... */ }
```

Dynamic path params and nesting use axum 0.8 syntax:

```rust
let items = Router::new().route("/{id}", get(get_item));
let app = Router::new().nest("/api/v1/items", items);
```

## Testing handlers

Test routers without a live server via `tower::ServiceExt::oneshot`:

```rust
use tower::ServiceExt; // for `.oneshot`

let response = app.oneshot(
    Request::builder().uri("/products").body(Body::empty()).unwrap()
).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
```

See `tests/auth_admin.rs` for an existing integration example.

## Checklist

- [ ] Route paths use `{param}` syntax; layers attached after their routes
- [ ] State passed via `State<T>`; one body extractor, last in the arg list
- [ ] Errors use the rejection shape of the file (`(StatusCode, String)` or `(StatusCode, Json<ErrorResponse>)`)
- [ ] Request DTOs validated with `garde` before DB access
- [ ] Protected routes layered with `AuthLayer` and (where needed) `RequireRoleLayer`
- [ ] No `unwrap()`/`expect()` in handlers (see `architecture.md` "What NOT to do")

## References

- `references/axum_middleware.md` — *Axum Middleware: A Beginner's Complete Guide* (Syeed Talha) — middleware, `from_fn`, layering, layer order.
- `references/axum_framework_guide.md` — *Axum Framework: The Ultimate Guide* (Mastering Backend) — design philosophy, routing, nesting, error handling, REST basics.
Pliki w paczce (3)
  • SKILL.md 10 KB
  • references/axum_framework_guide.md 7 KB
  • references/axum_middleware.md 10 KB

Wersje

W bazie nie ma innych skilli o nazwie axum-patterns.

Migawka bazy z 13.11.2026 · wersja paczki f70d9de061cf (skrót SHA-256 całej paczki)

Pobrania w ostatnich 30 dniach: 0 · ranking

Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.

Podobne skille

  • 100axum-idioms
    Opisuje wzorce frameworka Axum 0.8+: typowane ekstraktory, middleware Tower, routing i łagodne zamykanie przy budowie API HTTP i usług webowych w Rust.
  • 90rc-axum
    Pisze i sprawdza API Axum 0.8+ w Rust: routing, stan, ekstraktory, middleware Tower i obsługę błędów.
  • 100axum-code-review
    Recenzuje kod frameworka axum pod kątem routingu, użycia extractorów, middleware, zarządzania stanem i obsługi błędów, w tym wzorców axum 0.7+ (State, Path, Query, Json).
  • 100axum
    Dostarcza zasady i wzorce pracy z frameworkiem Axum w Rust: integracja z Tower, asynchroniczne handlery, ekstraktory i typowane routowanie.
  • 100axum
    Buduje API HTTP i usługi webowe w Rust z Axum: trasy, extractory, handlery JSON, współdzielony stan, middleware, błędy, WebSockety, graceful shutdown, testy i migracja kodu 0.6 i 0.7 do 0.8.
  • 100rust-web-backend
    Pomaga budować backend REST i HTTP w Rust: routing axum, extractory, współdzielony stan, middleware, odpowiedzi błędów i dostęp do bazy przez sqlx.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.