Build Rust Actix Web APIs with Claude (2026)
Actix Web remains one of the most performant web frameworks in the Rust ecosystem When combined with Claude Code’s AI capabilities, you can rapidly prototype, develop, and test REST APIs while using Rust’s memory safety guarantees. This guide covers building practical APIs using Claude Code sessions and relevant skills.
Why Actix Web for Rust APIs
Before getting into code, it is worth understanding why teams choose Actix Web over alternatives. The Rust web framework landscape includes several capable options, Axum, Warp, Rocket, and Tide are all production-ready. Here is how they compare on the axes that matter most:
| Framework | Performance | Async Model | Learning Curve | Ecosystem Maturity |
|---|---|---|---|---|
| Actix Web | Excellent | Actor-based, Tokio | Moderate | High |
| Axum | Excellent | Tokio-native | Low-moderate | High |
| Rocket | Good | Tokio | Low | High |
| Warp | Very good | Tokio filter chain | High | Moderate |
Actix Web wins on benchmark throughput and ecosystem breadth. It has mature middleware, WebSocket support, multipart handling, and a large collection of third-party integrations. For APIs that need to handle sustained high request volumes, think 50k+ req/s on modest hardware, it is hard to beat.
The tradeoff is that Actix’s actor system adds conceptual overhead early in the learning curve. Claude Code helps flatten that curve by generating idiomatic patterns you can inspect and adapt.
Setting Up Your Rust API Project
Initialize a new Actix Web project using Cargo:
cargo new rust-api-guide
cd rust-api-guide
cargo add actix-web tokio serde serde_json serde_derive
cargo add actix-rt --features macros
Configure your Cargo.toml dependencies:
[dependencies]
actix-web = "4"
actix-rt = "2"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
For a production API you will also want logging, environment configuration, and a connection pool. Add these upfront to avoid refactoring later:
[dependencies]
actix-web = "4"
actix-rt = "2"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
env_logger = "0.11"
log = "0.4"
dotenvy = "0.15"
uuid = { version = "1", features = ["v4", "serde"] }
Creating Your First Endpoint
Define a simple REST endpoint with request validation. Here’s a practical example of a user management endpoint:
// src/main.rs
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
#[derive(Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
async fn get_users() -> impl Responder {
let users = vec![
User { id: 1, name: "Alice".to_string(), email: "[email protected]".to_string() },
User { id: 2, name: "Bob".to_string(), email: "[email protected]".to_string() },
];
HttpResponse::Ok().json(users)
}
async fn create_user(req: web::Json<CreateUserRequest>) -> impl Responder {
let new_user = User {
id: 3,
name: req.name.clone(),
email: req.email.clone(),
};
HttpResponse::Created().json(new_user)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/users", web::get().to(get_users))
.route("/users", web::post().to(create_user))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Run the server with cargo run and test with curl:
curl http://127.0.0.1:8080/users
curl -X POST http://127.0.0.1:8080/users \
-H "Content-Type: application/json" \
-d '{"name": "Charlie", "email": "[email protected]"}'
Structuring Routes with App State
In-memory data is fine for prototypes, but real applications need shared state, database pools, configuration, caches. Actix Web’s web::Data wrapper handles this cleanly:
use actix_web::{web, App, HttpResponse, HttpServer};
use std::sync::Mutex;
struct AppState {
users: Mutex<Vec<User>>,
request_count: Mutex<u64>,
}
async fn get_users(data: web::Data<AppState>) -> HttpResponse {
let users = data.users.lock().unwrap();
HttpResponse::Ok().json(users.clone())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let state = web::Data::new(AppState {
users: Mutex::new(vec![]),
request_count: Mutex::new(0),
});
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.route("/users", web::get().to(get_users))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Ask Claude Code to scaffold this pattern for any new project:
claude "Scaffold an Actix Web app with web::Data state containing a connection pool
and a request metrics counter. Show the main function, state struct, and one
example handler that increments the counter."
Claude produces the boilerplate correctly, including the move closure in HttpServer::new that trips up many developers new to Rust’s ownership rules.
Using Claude Code with Your API
Activate Claude Code and use specific skills to enhance your development workflow. The /tdd skill helps generate unit tests for your endpoint handlers:
/tdd
Write tests for a create_user handler that validates email format and rejects empty names
The skill guides Claude to produce test cases covering validation logic, edge cases, and error responses.
For API documentation, use the /pdf skill to generate structured documentation from your OpenAPI specs or code comments:
/pdf
Generate API documentation from the OpenAPI spec in ./api-spec.yaml
Claude Code is particularly useful when you hit Rust-specific roadblocks. Common examples:
Lifetime errors in handler functions
claude "My Actix handler returns a reference to data inside a Mutex guard.
The compiler says 'cannot return reference to temporary value'. Explain why and show the fix."
Async trait bounds
claude "I need to pass a trait object that implements an async method to an Actix handler.
Show the pattern for boxing the future and making this work with web::Data."
Middleware ordering
claude "Explain the order in which Actix Web applies middleware when you chain
wrap() calls on an App. Does order matter for authentication + logging?"
These targeted questions get precise answers that would otherwise require significant time reading the Actix documentation and source code.
Advanced: Database Integration with Diesel
Connect your Actix Web API to a PostgreSQL database using Diesel ORM:
// src/models.rs
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Queryable, Serialize, Deserialize)]
pub struct User {
pub id: i32,
pub username: String,
pub email: String,
}
#[derive(Insertable)]
#[table_name = "users"]
pub struct NewUser<'a> {
pub username: &'a str,
pub email: &'a str,
}
Configure connection pooling in your main.rs:
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;
type DbPool = Pool<ConnectionManager<PgConnection>>;
async fn get_all_users(pool: web::Data<DbPool>) -> impl Responder {
use crate::schema::users::dsl::*;
let conn = pool.get().unwrap();
let results = users.load::<User>(&conn).unwrap();
HttpResponse::Ok().json(results)
}
In practice you will want to handle the pool error and the query error without unwrapping. Here is a more production-ready version that returns proper HTTP status codes on failure:
use actix_web::web;
use diesel::r2d2::{ConnectionManager, Pool, PoolError};
use diesel::PgConnection;
use diesel::prelude::*;
type DbPool = Pool<ConnectionManager<PgConnection>>;
async fn get_all_users(pool: web::Data<DbPool>) -> HttpResponse {
let conn = match pool.get() {
Ok(c) => c,
Err(_) => return HttpResponse::ServiceUnavailable()
.json(serde_json::json!({"error": "Database unavailable"})),
};
match users.load::<User>(&conn) {
Ok(results) => HttpResponse::Ok().json(results),
Err(e) => {
log::error!("Database query failed: {}", e);
HttpResponse::InternalServerError()
.json(serde_json::json!({"error": "Query failed"}))
}
}
}
Ask Claude to audit your handlers for unwrap calls before shipping:
claude "Review all my Actix handlers and identify every .unwrap() call.
For each one, suggest the appropriate error handling pattern and whether it
should return 400, 500, or 503."
Error Handling Patterns
Implement consistent error responses across your API by defining a custom error type that implements actix_web::ResponseError:
use actix_web::HttpResponse;
use serde::Serialize;
use std::fmt;
#[derive(Debug, Serialize)]
pub struct ApiError {
pub code: String,
pub message: String,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}: {}", self.code, self.message)
}
}
impl actix_web::ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
match self.code.as_str() {
"NOT_FOUND" => HttpResponse::NotFound().json(self),
"VALIDATION_ERROR" => HttpResponse::BadRequest().json(self),
"UNAUTHORIZED" => HttpResponse::Unauthorized().json(self),
_ => HttpResponse::InternalServerError().json(self),
}
}
}
// Usage in a handler
async fn get_user(path: web::Path<u64>) -> Result<HttpResponse, ApiError> {
let user_id = path.into_inner();
find_user(user_id).ok_or_else(|| ApiError {
code: "NOT_FOUND".to_string(),
message: format!("User {} not found", user_id),
})
.map(|u| HttpResponse::Ok().json(u))
}
This pattern centralizes HTTP status code decisions. When you add a new error variant, you update one match arm, not every handler that might produce that error.
Use this pattern with the /tdd skill to ensure your error paths are tested:
/tdd
Generate test cases for the create_user endpoint covering: valid input, missing name, invalid email format, database connection failure
The generated tests should verify not just that an error occurs, but that the response body has the correct code field and that the HTTP status code matches your contract.
Input Validation with the Validator Crate
Raw Serde deserialization catches type mismatches but not semantic invalidity, an empty string is a valid String in Serde. Use the validator crate for domain-level validation:
[dependencies]
validator = { version = "0.18", features = ["derive"] }
use serde::Deserialize;
use validator::Validate;
#[derive(Deserialize, Validate)]
pub struct CreateUserRequest {
#[validate(length(min = 1, max = 100, message = "Name must be 1-100 characters"))]
pub name: String,
#[validate(email(message = "Must be a valid email address"))]
pub email: String,
#[validate(range(min = 18, max = 120, message = "Age must be between 18 and 120"))]
pub age: Option<u32>,
}
async fn create_user(
req: web::Json<CreateUserRequest>,
) -> Result<HttpResponse, ApiError> {
req.validate().map_err(|e| ApiError {
code: "VALIDATION_ERROR".to_string(),
message: e.to_string(),
})?;
// proceed with validated data
Ok(HttpResponse::Created().json(&*req))
}
Ask Claude to generate validation rules for any domain model:
claude "Add validator annotations to this Rust struct for a product listing:
name (required, 1-200 chars), price (positive float), sku (alphanumeric, 6-20 chars),
description (optional, max 5000 chars), category_ids (non-empty list of positive integers)"
Using supermemory for API Patterns
The /supermemory skill stores your preferred API patterns across sessions. Store common patterns:
/supermemory store: API error response format uses { "code": "ERROR_CODE", "message": "Human readable message" }. Standard pagination params are page and limit.
Claude remembers these patterns in future sessions, applying consistent designs across your API endpoints.
Additional patterns worth storing:
/supermemory store: Actix Web handler signature for authenticated endpoints uses
web::ReqData<AuthUser> extracted from JWT middleware. Never access the Authorization
header directly in handlers.
/supermemory store: All database operations go through a repository trait.
Handlers receive web::Data<dyn UserRepository> not a direct pool reference.
This makes handlers testable without a real database.
The repository pattern note is especially valuable. When you start a new service six weeks later and ask Claude to scaffold handlers, it will apply the pattern automatically without you having to re-explain it.
Combining Multiple Skills
For full-stack API development, combine skills effectively:
- Use
/tddto generate tests before implementing handlers - Apply
/frontend-designto build accompanying React/Vue components that consume your API - Use
/pdfto export API documentation for stakeholders - Reference
/supermemoryto maintain consistent error formats and naming conventions
Example workflow:
/tdd
Write handler tests for a product inventory endpoint
/frontend-design
Scaffold a React component that displays inventory data with pagination
/pdf
Create API documentation from the spec
A more complete session flow for a new API resource looks like:
1 - Generate the data model and migrations
claude "Generate a Diesel model and migration for a 'product' table with:
id (uuid), name (varchar 200), price (numeric 10,2), stock_quantity (integer),
created_at and updated_at timestamps. Include the Up and Down migration SQL."
2 - Scaffold CRUD handlers with proper error handling
claude "Scaffold Actix Web CRUD handlers for the Product model using the repository
pattern. Return 404 on missing resources, 422 on validation failure, 503 on
database unavailability."
3 - Generate tests using TDD skill
/tdd
Write integration tests for each Product CRUD endpoint using actix_web::test
4 - Document the API
/pdf
Generate OpenAPI 3.0 documentation for the Product CRUD endpoints
Each step is independent and reversible. If the generated migration looks wrong, fix it before generating the handlers. Claude Code gives you checkpoints rather than a single opaque output.
Step-by-Step Guide: Building Your First Actix Web API with Claude Code
Here is a concrete workflow for building a production-ready Actix Web API from scratch using Claude Code at every stage.
Step 1. Project scaffolding. Open a Claude Code session in your new project directory. Ask it to generate a complete Cargo.toml with all necessary dependencies: actix-web, actix-rt, tokio with full features, serde with derive, serde_json, and uuid with v4 feature. Claude Code produces the correct dependency versions and avoids common incompatibility issues between async runtimes.
Step 2. Define your data models. Describe your domain in plain language. Claude Code generates Rust structs with appropriate Serde derives, proper field types, and consideration for JSON serialization edge cases like optional fields and default values.
Step 3. Implement handler functions. Use Claude Code with the /tdd skill to generate tests first, then implement each handler. The TDD workflow works particularly well with Actix Web because handler signatures are explicit about their dependencies, making test doubles straightforward.
Step 4. Wire up routing and middleware. Ask Claude Code to generate the App configuration block with your route definitions, CORS middleware, JSON error handlers, and request logging. Getting middleware order correct in Actix Web requires attention to detail that Claude Code handles consistently.
Step 5. Add database integration. Tell Claude Code which database you’re using and it generates the connection pool setup, connection extraction in handlers, and an example repository pattern to keep database logic separate from HTTP handling.
Common Pitfalls
Forgetting to call .await on async handlers. Actix Web handlers must be async functions, but forgetting to await inner futures produces compilation errors with confusing messages. Claude Code generates correctly awaited handler bodies and the /tdd skill writes tests that verify async behavior.
Using unwrap() in production handlers. Rust’s unwrap panics the thread on None or Err, which Actix Web converts into a 500 error with no useful response. Use the ? operator with a custom error type that implements ResponseError. Claude Code can generate this error type and the required trait implementations automatically.
Blocking the async runtime. CPU-intensive operations in Actix handlers block the tokio runtime. For heavy computation, use web::block() to move work to a thread pool. Claude Code recognizes when operations are synchronous and wraps them appropriately.
Not validating request bodies. The web::Json<T> extractor returns a 400 error if deserialization fails, but the error message is generic. Add a custom JSON error handler to your App configuration that provides specific validation messages. Claude Code generates this handler as part of a standard app setup.
Ignoring connection pool exhaustion. When every handler holds a database connection, pool exhaustion causes request timeouts under load. Claude Code can review your handler patterns and suggest connection borrowing scopes that release connections as quickly as possible.
Best Practices
Use custom error types instead of Box
Extract shared state into Data
Structure routes by resource, not HTTP method. Group related routes together using web::scope() rather than a flat list of route registrations. Claude Code generates scope-based routing that scales cleanly as your API grows.
Add structured logging with tracing. The tracing crate integrates with Actix Web’s request logging and provides structured, queryable logs. Claude Code can add tracing instrumentation to your handlers and configure the subscriber for both development and production environments.
Write integration tests with actix-web’s test utilities. The actix-web::test module provides an HTTP test client that exercises your actual App configuration. Claude Code with the /tdd skill generates integration tests that cover authentication, error responses, and business logic without a running server.
Advanced: State Management and Middleware
For production APIs, you need request-level state management and cross-cutting middleware:
use actix_web::middleware::Logger;
use actix_web::web::Data;
use std::sync::Arc;
struct AppState {
db_pool: sqlx::PgPool,
config: Arc<Config>,
}
async fn build_app(state: web::Data<AppState>) -> App<...> {
App::new()
.app_data(state)
.wrap(Logger::default())
.wrap(actix_cors::Cors::permissive())
.service(
web::scope("/api/v1")
.service(users_routes())
.service(products_routes())
)
}
Claude Code generates this configuration with proper type annotations and handles the lifetime complexity that async Actix Web applications require.
Integration Patterns
Combining with the /supermemory skill. Store your API conventions, error format, pagination parameters, authentication patterns, in supermemory at the start of a project. Claude Code retrieves these conventions in future sessions, ensuring consistency across all endpoints even when working on different parts of the API weeks apart.
Generating OpenAPI documentation. Use the utoipa crate with Claude Code to generate OpenAPI spec directly from your handler definitions. Claude Code adds the required macro annotations to your structs and handler functions, producing accurate API documentation that stays synchronized with your implementation.
Connecting to a React frontend. Use the /frontend-design skill to scaffold React components that consume your Actix Web API. The skill generates typed fetch functions based on your API response shapes, keeping the frontend and backend in sync.
Conclusion
Claude Code accelerates Actix Web development by generating tests, documenting APIs, and maintaining consistent patterns across your codebase. The combination of Rust’s performance and AI-assisted development creates a productive workflow for building high-performance web services.
The key is invoking the right skill at the appropriate time: /tdd for test generation, /pdf for documentation, and /supermemory for pattern retention. Your Actix Web APIs benefit from both Rust’s compile-time safety and Claude’s development velocity.
Where Claude Code adds the most value in Rust specifically is in the error-handling and ownership areas where new Rust developers get stuck. Instead of fighting the borrow checker alone, you can ask Claude to explain the error, suggest a fix, and then explain why that fix works, turning compiler errors into learning moments rather than blockers. Over time you internalize the patterns and need Claude less for the basics, but it remains useful for generating the boilerplate that Rust requires even for experienced developers.
Related Reading
- Best Practices Guide. Production-ready Claude Code guidelines and patterns
- Best Claude Skills for Developers in 2026. Full developer skill stack
- Automated Testing Pipeline with Claude TDD Skill. CI/CD integration
- Frontend Design Skills for React Development. Building UI layers
- Claude Code for Tonic gRPC Rust Workflow Guide
- Claude Code for Health Endpoint Pattern Workflow
- Claude Code Webhook Handler Tutorial Guide
- FastAPI Pydantic V2 Validation with Claude Code
- Claude Code for Web Share API Workflow Tutorial
- Claude Code for AsyncAPI Event-Driven Workflow Guide
- Claude Code FastAPI Dependency Injection Patterns Guide
- Claude Code Webhook Handler Implementation Workflow Guide
Built by theluckystrike. More at zovo.one
Try it: Paste your error into our Error Diagnostic for an instant fix.
Get started → Generate your project setup with our Project Starter.
Frequently Asked Questions
Why Actix Web for Rust APIs?
Actix Web wins on benchmark throughput and ecosystem breadth compared to alternatives like Axum, Rocket, and Warp. It delivers excellent performance through an actor-based async model on Tokio, supports sustained 50k+ requests per second on modest hardware, and provides mature middleware, WebSocket support, and multipart handling. The tradeoff is that Actix’s actor system adds conceptual overhead to the learning curve. Claude Code helps flatten that curve by generating idiomatic patterns developers can inspect and adapt.
What is Setting Up Your Rust API Project?
Setting up an Actix Web project requires initializing with cargo new and adding dependencies: actix-web = "4", actix-rt = "2", tokio with full features, serde with derive, and serde_json. For production APIs, also add env_logger, log, dotenvy for environment configuration, and uuid with v4 and serde features. Configure all dependencies upfront in Cargo.toml to avoid refactoring later, particularly logging and connection pool libraries.
What is Creating Your First Endpoint?
Creating your first Actix Web endpoint involves defining Serde-derived request and response structs (like User and CreateUserRequest), implementing async handler functions that return impl Responder, and wiring routes in HttpServer::new using web::get().to() and web::post().to(). The handler deserializes JSON request bodies via web::Json<T> extractor and returns responses with HttpResponse::Ok().json() or HttpResponse::Created().json(). Test with curl against http://127.0.0.1:8080.
What is Structuring Routes with App State?
Structuring routes with app state uses Actix Web’s web::Data wrapper to share database pools, configuration, and caches across handlers. Define an AppState struct with Mutex-wrapped fields, wrap it in web::Data::new(), and pass it via .app_data(state.clone()) in the App configuration. Handlers extract state through web::Data<AppState> parameters. Claude Code generates this pattern correctly, including the move closure in HttpServer::new that trips up developers new to Rust’s ownership rules.
What is Using Claude Code with Your API?
Claude Code enhances Actix Web development through targeted skills: the /tdd skill generates unit tests covering validation logic, edge cases, and error responses; the /pdf skill generates API documentation from OpenAPI specs; and the /supermemory skill stores preferred API patterns like error response formats and pagination parameters across sessions. Claude Code is especially valuable for resolving Rust-specific issues like lifetime errors in handler functions, async trait bounds, and middleware ordering questions.