Why Actix Web Needs Framework-Specific Rules
Actix Web is Rust's highest-performance web framework — consistently topping the TechEmpower benchmarks. But its ownership-aware API confuses AI assistants: shared state requires Arc<Mutex<T>> or web::Data<T>, extractors need correct ordering in handler signatures, and error handling requires implementing ResponseError. AI generates Express/Gin patterns that don't compile in Rust.
The most common AI failures: trying to share mutable state without proper synchronization (compiler rejects it), wrong extractor ordering in handler parameters, manual JSON parsing instead of using serde extractors, panic-based error handling instead of Result + ResponseError, and creating one giant configure function instead of modular ServiceConfig.
These rules layer on top of Rust language rules. Actix Web has its own idioms on top of Rust's — extractors, app data, guards, middleware, and the configure pattern for modular routing.
Rule 2: Typed Extractors for Request Data
The rule: 'Use typed extractors for all request data. web::Path<T> for path params: async fn get_user(path: web::Path<(u64,)>). web::Query<T> for query params: async fn search(query: web::Query<SearchParams>). web::Json<T> for JSON body: async fn create(body: web::Json<CreateUser>). All extractors use serde for deserialization and validate types automatically.'
For custom extractors: 'Create FromRequest implementations for complex extraction logic (auth tokens, pagination, custom headers). Custom extractors can return errors that map to HTTP responses. Use them like built-in extractors in handler signatures.'
For extractor configuration: 'Configure JSON payload limits: web::JsonConfig::default().limit(4096). Configure path error handling: web::PathConfig::default().error_handler(|err, req| { ... }). Configure at the App or scope level — not per route.'
Rule 3: Modular Routing with ServiceConfig
The rule: 'Use ServiceConfig functions for modular route registration: fn user_routes(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("/users").route(web::get().to(list_users)).route(web::post().to(create_user))); }. Register in main: App::new().configure(user_routes).configure(order_routes). Each domain has its own configure function in a separate file.'
For route organization: 'Group related routes with web::scope: web::scope("/api/v1").configure(user_routes).configure(order_routes). Apply middleware to scopes: web::scope("/admin").wrap(AdminGuard). Use web::resource for CRUD: web::resource("/users/{id}").route(web::get().to(get)).route(web::put().to(update)).route(web::delete().to(delete)).'
AI puts all routes in main() as a flat list. ServiceConfig functions are Actix's module system — each configure function is testable, reusable, and independently maintainable. Use them like Express routers or Gin route groups.
ServiceConfig functions are Actix's module system. Each domain gets a configure fn in its own file: user_routes(cfg), order_routes(cfg). Compose in main: App::new().configure(users).configure(orders). Testable and independent.
Rule 4: Error Handling with ResponseError
The rule: 'Define custom error types that implement actix_web::ResponseError: #[derive(Debug, thiserror::Error)] enum AppError { #[error("Not found")] NotFound, #[error("Validation failed: {0}")] Validation(String), #[error("Internal error")] Internal(#[from] anyhow::Error) }. Implement ResponseError to map errors to HTTP responses: fn error_response(&self) -> HttpResponse and fn status_code(&self) -> StatusCode.'
For handler returns: 'Handlers return Result<impl Responder, AppError>. Use ? for error propagation: let user = db.get_user(id).await.map_err(|_| AppError::NotFound)?;. This is idiomatic Rust error handling adapted for HTTP — errors propagate with ? and convert to HTTP responses via ResponseError.'
AI generates panic!() or unwrap() in handlers — crashing the worker thread on any error. Result + ResponseError is the correct Actix pattern: errors become HTTP responses, not crashes.
- Custom error enum with thiserror — implements ResponseError
- status_code() maps error variants to HTTP status codes
- error_response() creates the HTTP response body
- ? operator for propagation — map_err for conversion
- Never unwrap() or panic!() in handlers — Result<T, AppError> always
unwrap() panics on error — crashing the Actix worker thread. Result<T, AppError> with ? propagation converts errors to HTTP responses via ResponseError. The handler stays alive, the user gets a proper error.
Rule 5: Middleware and Guards
The rule: 'Use .wrap() for middleware on App or scope: App::new().wrap(Logger::default()).wrap(Cors::default()). Use guards for route-level conditions: web::get().guard(guard::Header("content-type", "application/json")). Custom middleware implements Transform + Service traits — use actix-web-lab's from_fn for simpler middleware: .wrap(from_fn(my_middleware)).'
For common middleware: 'actix-web Logger for request logging, actix-cors for CORS, actix-web-lab for utilities. For auth middleware, create a middleware that extracts the token, validates it, inserts the user into request extensions, and calls next. Access user in handlers: req.extensions().get::<User>().'
For testing: 'Use actix_web::test for handler testing: let app = test::init_service(App::new().configure(user_routes)).await. Send test requests: let resp = test::call_service(&app, req).await. Assert status and body: assert_eq!(resp.status(), StatusCode::OK). No server needed — test the full middleware + handler pipeline in-process.'
Complete Actix Web Rules Template
Consolidated rules for Actix Web projects (use alongside Rust language rules).
- web::Data<T> for shared state — never global, never per-request connections
- Typed extractors: web::Path, web::Query, web::Json — serde for all deserialization
- ServiceConfig functions for modular routing — one configure fn per domain
- web::scope for grouped routes + scoped middleware — web::resource for CRUD
- Custom AppError + ResponseError — Result<T, AppError> on all handlers — never unwrap()
- .wrap() for middleware — guards for route conditions — from_fn for simple middleware
- actix_web::test for in-process testing — no server needed
- cargo clippy + cargo test in CI — actix-cors + Logger as baseline middleware