Skip to main content

Application State

Managing and sharing state across workers.

Actix 4.9+ 6 min readAdvanced Topic

# Application State

Modern web applications are rarely stateless. You need a way to pass database connections, configuration parameters, or caching layers to your request handlers. Actix Web's web::Data extractor solves this elegantly.

##Initializing State

State in Actix Web is tied to the App instance. Because HttpServer creates a new App for every worker thread, you must wrap state in web::Data before passing it into the server factory if you want that state shared globally.

code_blocksrust
use actix_web::{web, App, HttpServer};
use std::sync::Mutex;

struct AppState {
    app_name: String,
    counter: Mutex<u32>, // Mutex needed for mutation
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let state = web::Data::new(AppState {
        app_name: String::from("My Awesome App"),
        counter: Mutex::new(0),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(state.clone()) // Inject the state
            // ... routes ...
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

##Extracting State

Inside your handler, you use the web::Data extractor to access the state. The type signature MUST exactly match what you injected in main.

code_blocksrust
use actix_web::{web, Responder};

async fn index(data: web::Data<AppState>) -> impl Responder {
    let mut count = data.counter.lock().unwrap();
    *count += 1;
    
    format!("Welcome to {}, visitor number {}!", data.app_name, count)
}

##Common Use Case: Database Pools

The most common item placed in App State is a database connection pool (like sqlx::PgPool or r2d2). Since connection pools are inherently designed to be safely shared across threads, you often do not need to wrap them in a Mutex.

code_blocksrust
// Assuming sqlx::PgPool
// let pool = PgPoolOptions::new().connect("postgres://...").await?;
// let data = web::Data::new(pool);

Inside the handler, you can extract web::Data<PgPool> and pass a reference to it to your SQL queries.

##Thread-Local State

If you actually want state to be unique per worker thread (e.g., a thread-local cache), you initialize it inside the HttpServer::new() closure, rather than outside.

Was this page helpful?

Reference Implementation

The following code snippet demonstrates the authoritative pattern for this documentation.

src/main.rs
1use actix_web::{get, web, App, HttpServer, Responder};
2
3struct Config {
4 environment: String,
5}
6
7#[get("/env")]
8async fn env_check(config: web::Data<Config>) -> impl Responder {
9 format!("Running in {} mode", config.environment)
10}
11
12#[actix_web::main]
13async fn main() -> std::io::Result<()> {
14 let config = web::Data::new(Config {
15 environment: "Production".to_string(),
16 });
17
18 HttpServer::new(move || {
19 App::new()
20 .app_data(config.clone())
21 .service(env_check)
22 })
23 .bind("127.0.0.1:8080")?
24 .run()
25 .await
26}
Was this page helpful?