[Part 5] Setting up Actix

Let's setup Actix and for the purpose of the future freshness of the article, we will be using the beta version of the actix-web crate and soon the stable version would be released. For more info about what Actix is, refer to their official docs here

Add the dependency to your cargo.toml as shown below

[package]
name = "todo-actix-seaorm"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
sea-orm = { version = "^0", features = [ "sqlx-mysql", "runtime-actix-rustls", "macros", "debug-print" ], default-features = false }
dotenv = "0.15.0"
chrono = "0.4"
serde = "1"
actix-web = "4.0.0-beta.19"

It's time for the main

Make sure your main is modified to look like below code

mod entity;
mod repository;

use crate::repository::prelude::TodosRepository;
use actix_web::{
    middleware,
    web::{self, Data},
    App, HttpServer,
};

#[derive(Debug, Clone)]
pub struct AppState {
    todo_repository: TodosRepository,
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // ENV Setup
    std::env::set_var("RUST_LOG", "debug");
    dotenv::dotenv().ok();
    let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
    let db_conn = sea_orm::Database::connect(&db_url).await.unwrap();
    let host = std::env::var("HOST").expect("HOST is not set in .env file");
    let port = std::env::var("PORT").expect("PORT is not set in .env file");
    let server_url = format!("{}:{}", host, port);

    // App state setup
    let todo_repository = TodosRepository {
        db_conn: db_conn.clone(),
    };
    let state = AppState { todo_repository };

    let server = HttpServer::new(move || {
        App::new()
            .app_data(Data::new(state.clone()))
            .wrap(middleware::Logger::default())
            .configure(init)
    })
    .bind(&server_url)?;

    println!("Starting server at {}", server_url);
    server.run().await?;
    Ok(())
}

pub fn init(cfg: &mut web::ServiceConfig) {

}

This is mostly going to be our main file from now on and let's go through some explanations

  1. These are the modules that are available in our project

    mod entity;
    mod repository;
    
  2. Here we are importing our TodosRepository from the repository module prelude and other required imports from the actix crate which we will see below

    use crate::repository::prelude::TodosRepository;
    use actix_web::{
     middleware,
     web::{self, Data},
     App, HttpServer,
    };
    use handler::prelude::todos_handler;
    
  3. We are creating an AppState which is shared across all our APIs and the API handlers can use these to call the repositories internally. For brevity I have just added our todo_repository as a state but feel free to be creative and example use cases can be, here we can also add some other function as a dependency which can trigger logs, notifications, message to a queue, etc. Notice the #[derive(Debug, Clone)] on top of the struct, it is important as it helps us in deriving base implementations for the fields of the struct. We want our fields to have the ability of cloning and hence we added the Clone trait. This is because, the API requests are handled by separate threads and hence this state is cloned and passed to the threads. Since the variables are immutable by default we can be confident that no API handler will be able to modify or nullify anything. The Actix framework also supports mutable app state in a thread safe way.

    #[derive(Debug, Clone)]
    pub struct AppState {
     todo_repository: TodosRepository,
    }
    
  4. Now comes the main function where mark it as async and attach a runtime main macro to it (Please go through the previously mentioned async blog post for understand more why that is done). We setup the environment variables namely DATABASE_URL, HOST, PORT. We then create a database connection with the DATABASE_URL and create the todo_repository. Then the app state is constructed with the todo_repository. In real life scenarios, maybe the whole initialization and setup can be moved to a separate module for more readability. Finally we create the HttpServer and attach the state, logging middle ware to it. The server is then run and we get a message that says so. In case of any issues while starting the server, the main function would panic and hence it returns std::io::Result the program will exit and display the error of why the server failed to start.

    App::new()
             .app_data(Data::new(state.clone()))
             .wrap(middleware::Logger::default())
             .configure(init)
    
    pub fn init(cfg: &mut web::ServiceConfig) {
     cfg.service(todos_handler());
    }
    

Notice the configure block where we pass a function to it, this is where we will be configuring our handlers.

Conclusion

We have setup our server using actix succesfully and now we need to create handlers for handling our request and see how we connect the handlers to use repositories in the next part !

Did you find this article valuable?

Support Omprakash Sridharan by becoming a sponsor. Any amount is appreciated!