Buckets:
| name: actix-web | |
| description: | | |
| Actix Web is a powerful, high-performance web framework for Rust. It provides | |
| async handlers, type-safe extractors, middleware, and integrates with the Rust | |
| ecosystem for building fast, reliable, and memory-safe web services. | |
| license: Apache-2.0 | |
| compatibility: 'rust >= 1.72, cargo' | |
| metadata: | |
| author: terminal-skills | |
| version: 1.0.0 | |
| category: development | |
| tags: | |
| - rust | |
| - web | |
| - api | |
| - async | |
| - performance | |
| - type-safe | |
| # Actix Web | |
| Actix Web is one of the fastest web frameworks available. It uses Rust's type system for compile-time safety, async/await for concurrency, and extractors for ergonomic request handling. | |
| ## Installation | |
| ```toml | |
| # Cargo.toml — dependencies | |
| [dependencies] | |
| actix-web = "4" | |
| actix-rt = "2" | |
| serde = { version = "1", features = ["derive"] } | |
| serde_json = "1" | |
| sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "macros"] } | |
| tokio = { version = "1", features = ["full"] } | |
| env_logger = "0.11" | |
| ``` | |
| ## Project Structure | |
| ``` | |
| # Recommended Actix Web project layout | |
| src/ | |
| ├── main.rs # Entry point and server config | |
| ├── config.rs # Configuration | |
| ├── routes/ | |
| │ ├── mod.rs # Route registration | |
| │ ├── articles.rs # Article handlers | |
| │ └── health.rs # Health check | |
| ├── models/ | |
| │ └── article.rs # Data structures | |
| ├── db/ | |
| │ └── article.rs # Database queries | |
| ├── middleware/ | |
| │ └── auth.rs # Auth middleware | |
| └── errors.rs # Error types | |
| ``` | |
| ## Main and Server Setup | |
| ```rust | |
| // src/main.rs — application entry point | |
| use actix_web::{web, App, HttpServer, middleware::Logger}; | |
| use sqlx::postgres::PgPoolOptions; | |
| mod routes; | |
| mod models; | |
| mod db; | |
| mod errors; | |
| #[actix_web::main] | |
| async fn main() -> std::io::Result<()> { | |
| env_logger::init(); | |
| let database_url = std::env::var("DATABASE_URL") | |
| .unwrap_or_else(|_| "postgres://localhost/mydb".to_string()); | |
| let pool = PgPoolOptions::new() | |
| .max_connections(10) | |
| .connect(&database_url) | |
| .await | |
| .expect("Failed to create pool"); | |
| HttpServer::new(move || { | |
| App::new() | |
| .wrap(Logger::default()) | |
| .app_data(web::Data::new(pool.clone())) | |
| .configure(routes::configure) | |
| }) | |
| .bind("0.0.0.0:8080")? | |
| .run() | |
| .await | |
| } | |
| ``` | |
| ## Route Configuration | |
| ```rust | |
| // src/routes/mod.rs — centralized route registration | |
| use actix_web::web; | |
| mod articles; | |
| mod health; | |
| pub fn configure(cfg: &mut web::ServiceConfig) { | |
| cfg.service( | |
| web::scope("/api") | |
| .route("/health", web::get().to(health::check)) | |
| .service( | |
| web::scope("/articles") | |
| .route("", web::get().to(articles::list)) | |
| .route("", web::post().to(articles::create)) | |
| .route("/{id}", web::get().to(articles::get)) | |
| .route("/{id}", web::delete().to(articles::delete)) | |
| ) | |
| ); | |
| } | |
| ``` | |
| ## Models | |
| ```rust | |
| // src/models/article.rs — data models with serde | |
| use serde::{Deserialize, Serialize}; | |
| use sqlx::FromRow; | |
| #[derive(Debug, Serialize, FromRow)] | |
| pub struct Article { | |
| pub id: i32, | |
| pub title: String, | |
| pub body: String, | |
| pub published: bool, | |
| pub created_at: chrono::NaiveDateTime, | |
| } | |
| #[derive(Debug, Deserialize)] | |
| pub struct CreateArticle { | |
| pub title: String, | |
| pub body: String, | |
| } | |
| #[derive(Debug, Deserialize)] | |
| pub struct ListParams { | |
| pub page: Option<u32>, | |
| pub limit: Option<u32>, | |
| } | |
| ``` | |
| ## Handlers with Extractors | |
| ```rust | |
| // src/routes/articles.rs — request handlers | |
| use actix_web::{web, HttpResponse}; | |
| use sqlx::PgPool; | |
| use crate::models::article::{Article, CreateArticle, ListParams}; | |
| use crate::errors::AppError; | |
| pub async fn list( | |
| pool: web::Data<PgPool>, | |
| query: web::Query<ListParams>, | |
| ) -> Result<HttpResponse, AppError> { | |
| let limit = query.limit.unwrap_or(20).min(100) as i64; | |
| let offset = ((query.page.unwrap_or(1) - 1) * limit as u32) as i64; | |
| let articles = sqlx::query_as::<_, Article>( | |
| "SELECT * FROM articles WHERE published = true ORDER BY created_at DESC LIMIT $1 OFFSET $2" | |
| ) | |
| .bind(limit) | |
| .bind(offset) | |
| .fetch_all(pool.get_ref()) | |
| .await?; | |
| Ok(HttpResponse::Ok().json(articles)) | |
| } | |
| pub async fn create( | |
| pool: web::Data<PgPool>, | |
| body: web::Json<CreateArticle>, | |
| ) -> Result<HttpResponse, AppError> { | |
| let article = sqlx::query_as::<_, Article>( | |
| "INSERT INTO articles (title, body) VALUES ($1, $2) RETURNING *" | |
| ) | |
| .bind(&body.title) | |
| .bind(&body.body) | |
| .fetch_one(pool.get_ref()) | |
| .await?; | |
| Ok(HttpResponse::Created().json(article)) | |
| } | |
| pub async fn get( | |
| pool: web::Data<PgPool>, | |
| path: web::Path<i32>, | |
| ) -> Result<HttpResponse, AppError> { | |
| let id = path.into_inner(); | |
| let article = sqlx::query_as::<_, Article>("SELECT * FROM articles WHERE id = $1") | |
| .bind(id) | |
| .fetch_optional(pool.get_ref()) | |
| .await? | |
| .ok_or(AppError::NotFound)?; | |
| Ok(HttpResponse::Ok().json(article)) | |
| } | |
| ``` | |
| ## Error Handling | |
| ```rust | |
| // src/errors.rs — custom error types | |
| use actix_web::{HttpResponse, ResponseError}; | |
| use std::fmt; | |
| #[derive(Debug)] | |
| pub enum AppError { | |
| NotFound, | |
| Internal(String), | |
| Database(sqlx::Error), | |
| } | |
| impl fmt::Display for AppError { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| AppError::NotFound => write!(f, "Not found"), | |
| AppError::Internal(msg) => write!(f, "{}", msg), | |
| AppError::Database(e) => write!(f, "Database error: {}", e), | |
| } | |
| } | |
| } | |
| impl ResponseError for AppError { | |
| fn error_response(&self) -> HttpResponse { | |
| match self { | |
| AppError::NotFound => HttpResponse::NotFound().json(serde_json::json!({"error": "not found"})), | |
| _ => HttpResponse::InternalServerError().json(serde_json::json!({"error": "internal error"})), | |
| } | |
| } | |
| } | |
| impl From<sqlx::Error> for AppError { | |
| fn from(e: sqlx::Error) -> Self { AppError::Database(e) } | |
| } | |
| ``` | |
| ## Middleware | |
| ```rust | |
| // src/middleware/auth.rs — simple auth middleware | |
| use actix_web::{dev::ServiceRequest, Error, HttpMessage}; | |
| use actix_web::error::ErrorUnauthorized; | |
| pub async fn validate_token(req: &ServiceRequest) -> Result<(), Error> { | |
| let token = req.headers() | |
| .get("Authorization") | |
| .and_then(|v| v.to_str().ok()) | |
| .and_then(|v| v.strip_prefix("Bearer ")); | |
| match token { | |
| Some(t) => { | |
| let user_id = verify_jwt(t).map_err(|_| ErrorUnauthorized("invalid token"))?; | |
| req.extensions_mut().insert(user_id); | |
| Ok(()) | |
| } | |
| None => Err(ErrorUnauthorized("missing token")), | |
| } | |
| } | |
| ``` | |
| ## Testing | |
| ```rust | |
| // tests/articles_test.rs — integration test | |
| use actix_web::{test, App, web}; | |
| #[actix_web::test] | |
| async fn test_list_articles() { | |
| let pool = setup_test_db().await; | |
| let app = test::init_service( | |
| App::new() | |
| .app_data(web::Data::new(pool)) | |
| .configure(routes::configure) | |
| ).await; | |
| let req = test::TestRequest::get().uri("/api/articles").to_request(); | |
| let resp = test::call_service(&app, req).await; | |
| assert_eq!(resp.status(), 200); | |
| } | |
| ``` | |
| ## Key Patterns | |
| - Use extractors (`web::Json`, `web::Path`, `web::Query`, `web::Data`) for type-safe request parsing | |
| - Implement `ResponseError` on custom error types for automatic HTTP error responses | |
| - Use `web::Data` for shared application state (DB pool, config) — it's cheaply cloneable | |
| - Use `sqlx` with compile-time checked queries (`sqlx::query!`) for production code | |
| - Use `configure` functions to modularize route registration | |
| - Use `#[actix_web::test]` for async integration tests with `test::init_service` | |
Xet Storage Details
- Size:
- 7.89 kB
- Xet hash:
- cca233c3d4cbd3aa1d15d5f91e7683de5eb3fefce400b249c75ea919dcf58b60
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.