Debug School

rakesh kumar
rakesh kumar

Posted on

Understanding Handler Functions in Rust: Role, Tasks, and Backend Architecture

Understanding Handler Functions in Rust
Why state, session, body, path, Multipart are used in handlers

Understanding Handler Functions in Rust

Handler functions are the heart of a Rust web backend built with Actix-Web. Every API request that reaches your server is processed by a handler. If main.rs starts and wires the application, handlers are the actual workers that process business logic.

This article explains:

What a handler function is (theory)

Its role in backend architecture

Its responsibilities

How it works in your project
Enter fullscreen mode Exit fullscreen mode

What Is a Handler Function?

In Actix-Web, a handler function is an async function that:

Receives HTTP request data

Validates or authenticates the request

Calls database/business logic

Returns an HTTP response
Enter fullscreen mode Exit fullscreen mode

It connects:

Client → Route → Handler → DB → Response
Enter fullscreen mode Exit fullscreen mode

Handlers do NOT:

Write SQL directly (DB layer does)

Start server (main.rs does)

Store global state (AppState does)

Handlers coordinate everything.

2️⃣ Where Handlers Sit in Backend Architecture

Here’s the architecture flow in your project:

Client (Browser / Postman)
        ↓
main.rs (route mapping)
        ↓
handlers/ (Controller Layer)
        ↓
db/ (Repository Layer)
        ↓
Database
Enter fullscreen mode Exit fullscreen mode

Handlers are the controller layer.

3️⃣ Responsibilities of Handler Functions

A handler typically performs these tasks:

✅ 1. Extract Request Data

web::Json<T> → JSON body

web::Path<T> → URL parameters

Session → login data

Multipart → file uploads

web::Data<AppState> → shared state (DB pool)
Enter fullscreen mode Exit fullscreen mode

✅ 2. Authenticate User

ensure_login(&session)?;
Enter fullscreen mode Exit fullscreen mode

✅ 3. Call Database Functions

list_brand_models(&state.pool).await
Enter fullscreen mode Exit fullscreen mode

✅ 4. Format Response

HttpResponse::Ok().json(response)
Enter fullscreen mode Exit fullscreen mode

4️⃣ Coding Example From Your Project

Let’s analyze your real handler:

Example 1: list_brand_models_handler

pub async fn list_brand_models_handler(
    state: web::Data<AppState>,
    session: Session,
) -> Result<HttpResponse> {
Enter fullscreen mode Exit fullscreen mode

🔹 Step 1: Extract dependencies

state → DB pool access

session → check login
Enter fullscreen mode Exit fullscreen mode

🔹 Step 2: Authenticate

ensure_login(&session)?;
Enter fullscreen mode Exit fullscreen mode

If not logged in → returns 401 Unauthorized.

🔹 Step 3: Call DB layer

let items = list_brand_models(&state.pool)
    .await
    .map_err(error::ErrorInternalServerError)?;
Enter fullscreen mode Exit fullscreen mode

This calls:

db/brand_model.rs → list_brand_models()

So handler does NOT write SQL.

🔹 Step 4: Build Response

let response = ApiResponse {
    message: "Brand models fetched".to_string(),
    data: items,
};
Enter fullscreen mode Exit fullscreen mode

🔹 Step 5: Return JSON

Ok(HttpResponse::Ok().json(response))
Enter fullscreen mode Exit fullscreen mode

🧠 Final Flow

GET /api/brand-models
    ↓
list_brand_models_handler
    ↓
ensure_login
    ↓
db::list_brand_models
    ↓
JSON response
Enter fullscreen mode Exit fullscreen mode

Example 2️⃣: Create Handler

From your project:


pub async fn create_brand_model_handler(
    body: web::Json<BrandModelForm>,
    state: web::Data<AppState>,
    session: Session,
) -> Result<HttpResponse> {
Enter fullscreen mode Exit fullscreen mode

🔹 Extract JSON body
body: web::Json

User sends:


{
  "name": "Toyota"
}

Enter fullscreen mode Exit fullscreen mode

Actix automatically converts JSON into BrandModelForm.

🔹 Authenticate

ensure_login(&session)?;
Enter fullscreen mode Exit fullscreen mode

🔹 Call DB function

create_brand_model(&state.pool, &body.into_inner())
    .await
Enter fullscreen mode Exit fullscreen mode

🔹 Return success response

Ok(HttpResponse::Created().json(response))
Enter fullscreen mode Exit fullscreen mode

Example 3️⃣: File Upload Handler

From your project:

pub async fn upload_brand_model_images_handler(
    mut payload: Multipart,
    session: Session,
)
Enter fullscreen mode Exit fullscreen mode

Purpose:


Accept file upload

Stream chunks

Save file to disk
Enter fullscreen mode Exit fullscreen mode

Loop through uploaded files:

while let Some(item) = payload.next().await {
Enter fullscreen mode Exit fullscreen mode

Each file is streamed piece by piece.

Generate unique filename:

let stored_name = format!(
    "vehicle_image_{}_{}{}",
    chrono::Utc::now().format("%Y%m%d%H%M%S"),
    Uuid::new_v4().simple(),
    ext
);
Enter fullscreen mode Exit fullscreen mode

This prevents file name conflicts.

Save file:

file.write_all(&data).await?;
Enter fullscreen mode Exit fullscreen mode

Return JSON:

Ok(HttpResponse::Ok().json(response))
Enter fullscreen mode Exit fullscreen mode

5️⃣ Authentication Handler Example (Login)

From your project:

pub async fn login(
    body: web::Json<LoginForm>,
    state: web::Data<AppState>,
    session: Session,
)
Enter fullscreen mode Exit fullscreen mode

Step 1: Find user in DB

let maybe_user = find_user_by_username(&state.pool, &body.username).await?;
Enter fullscreen mode Exit fullscreen mode

Returns:

Some(user) if found

None if not found

Step 2: Check credentials

if let Some(user) = maybe_user {

Enter fullscreen mode Exit fullscreen mode

Step 3: Store session

session.insert("username", user.username.clone())?;
session.insert("user_id", user.id)?;
session.insert("role", user.role.clone())?;
Enter fullscreen mode Exit fullscreen mode

Now user is logged in.

Step 4: Return response

Ok(HttpResponse::Ok().json(response))
Enter fullscreen mode Exit fullscreen mode

6️⃣ Why Handlers Are Important

Handlers:

Control API flow

Protect routes

Connect session with database

Format standard API response

Keep logic clean and structured

`Without handlers, your app would mix:`

Routing

SQL

Authentication

Response formatting
Enter fullscreen mode Exit fullscreen mode

All in one place → messy architecture.

7️⃣ C*lean Architecture Principle Used in Your Project*

Your structure follows this clean pattern:

main.rs → handlers → db → database
Enter fullscreen mode Exit fullscreen mode

Handlers act as:

Traffic controller

Security guard

Request manager

Response builder
Enter fullscreen mode Exit fullscreen mode

8️⃣ One-Line Summary

A handler function in Rust:

Receives request, verifies user, calls database logic, and returns structured response.

Why state, session, body, path, Multipart are used in handlers

Think of handler as:

A function that receives request and returns response.
Enter fullscreen mode Exit fullscreen mode

Different types of request need different inputs.

🔹 (A) state – Shared Data (like DB connection)
Why?

To access database inside handler.

Example

struct AppState {
    app_name: String,
}

fn handler(state: &AppState) {
    println!("App name is {}", state.app_name);
}
Enter fullscreen mode Exit fullscreen mode

Output

App name is My App
Enter fullscreen mode Exit fullscreen mode

In your project:

state.pool → DB connection
Enter fullscreen mode Exit fullscreen mode

session – Login information
Why?

To check if user is logged in.

Example

let username = Some("Ashwani");

if let Some(name) = username {
    println!("Welcome {}", name);
}
Enter fullscreen mode Exit fullscreen mode

Output

Welcome Ashwani
Enter fullscreen mode Exit fullscreen mode

In real app:

session.get("username")
Enter fullscreen mode Exit fullscreen mode

🔹 (C) body – JSON data from request
Why?

When user sends data like:

{
  "name": "Toyota"
}
Enter fullscreen mode Exit fullscreen mode

Example

struct Car {
    name: String,
}

let body = Car { name: "Toyota".to_string() };
println!("{}", body.name);
Enter fullscreen mode Exit fullscreen mode

Output

Toyota
Enter fullscreen mode Exit fullscreen mode

🔹 (D) path – ID from URL

URL:

/shops/5
Example
let path_id = 5;
println!("Shop id is {}", path_id);
Enter fullscreen mode Exit fullscreen mode

Output

Shop id is 5
Enter fullscreen mode Exit fullscreen mode

🔹 (E) Multipart – File Upload

When user uploads file.

Example idea

let file_name = "image.jpg";
println!("Saving file {}", file_name);
Enter fullscreen mode Exit fullscreen mode

Output
Saving file image.jpg

state: web::Data

Why: you need DB pool and shared app info.
Purpose: gives access to state.pool in handler.
When: use when handler needs DB.

Example (your code):

pub async fn list_shops_handler(
    state: web::Data<AppState>,
    session: Session,
) -> Result<HttpResponse> {
    let user_id = resolve_session_user_id(&session)?;
    let vendor_id =
        i32::try_from(user_id).map_err(|_| error::ErrorBadRequest("User id out of range"))?;

    let shops = list_shops_by_vendor(&state.pool, vendor_id)
        .await
        .map_err(error::ErrorInternalServerError)?;

    let response = ApiResponse {
        message: "Shops fetched".to_string(),
        data: shops,
    };

    Ok(HttpResponse::Ok().json(response))
}
Enter fullscreen mode Exit fullscreen mode

=====================OR======================

pub async fn create_shop_handler(
    body: web::Json<ShopForm>,
    state: web::Data<AppState>,
    session: Session,
) -> Result<HttpResponse> {
    let user_id = resolve_session_user_id(&session)?;
    println!("[create_shop_handler] session_user_id={}", user_id);
    let mut payload = body.into_inner();
    println!(
        "[create_shop_handler] payload_in shop_name={:?} partner_name={} vender_id={:?}",
        payload.shop_name, payload.partner_name, payload.vender_id
    );
    payload.vender_id =
        Some(i32::try_from(user_id).map_err(|_| error::ErrorBadRequest("User id out of range"))?);
    if let Some(shop_name) = payload.shop_name.clone() {
        if !shop_name.trim().is_empty() {
            payload.partner_name = shop_name;
        }
    }
    println!(
        "[create_shop_handler] payload_out shop_name={:?} partner_name={} vender_id={:?}",
        payload.shop_name, payload.partner_name, payload.vender_id
    );

    create_shop(&state.pool, &payload)
        .await
        .map_err(error::ErrorInternalServerError)?;

    let response = ApiResponse {
        message: "Shop created".to_string(),
        data: true,
    };

    Ok(HttpResponse::Created().json(response))
}
Enter fullscreen mode Exit fullscreen mode
pub async fn list_brand_models_handler(
    state: web::Data<AppState>,
    session: Session,
) -> Result<HttpResponse>
Enter fullscreen mode Exit fullscreen mode

Because this handler calls:

list_brand_models(&state.pool).await
Enter fullscreen mode Exit fullscreen mode

session: Session

Why: you want login/authentication using cookie-based session.
Purpose: read logged-in user info (username, user_id, role).
When: any route that should be protected (admin/vendor APIs).

Example:


ensure_login(&session)?;
Enter fullscreen mode Exit fullscreen mode

body: web::Json / web::Json

Why: client sends JSON data in request body.
Purpose: Actix parses JSON into a Rust struct.
When: POST/PUT requests where you create/update data.

Example:

pub async fn create_brand_model_handler(
    body: web::Json<BrandModelForm>,
    ...
)
Enter fullscreen mode Exit fullscreen mode

path: web::Path

Why: URL contains {id} like /brand-models/{id}
Purpose: extract the {id} parameter from the URL.
When: GET/PUT/DELETE single record endpoints.

Example:

pub async fn get_brand_model_handler(path: web::Path<i64>, ...)
Enter fullscreen mode Exit fullscreen mode

mut payload: Multipart

Why: file uploads are not JSON; they are multipart/form-data.
Purpose: stream file chunks and save to disk.
When: uploading images/documents.

Example:


pub async fn upload_vehicle_documents_handler(
    mut payload: Multipart,
    session: Session,
)
Enter fullscreen mode Exit fullscreen mode

mut is needed because you iterate/consume the stream:

while let Some(item) = payload.next().await { ... }

Why state.pool, path.into_inner(), body.into_inner()

These are wrappers.

🔹 state.pool

You stored DB inside state:

state.pool

It means:

Use database connection.

🔹 path.into_inner()

Path wraps value.

let path = 5;
let id = path;
println!("{}", id);
Enter fullscreen mode Exit fullscreen mode

Output:

5
🔹 body.into_inner()

Unwrap JSON.

let body = "Toyota";
let value = body;
println!("{}", value);
Enter fullscreen mode Exit fullscreen mode

Output:

Toyota
✅ 3️⃣

Why ensure_login(&session) in every function

Because you want only logged-in users.

Example

fn ensure_login(username: Option<String>) -> Result<String, String> {
    username.ok_or("Login required".to_string())
}
Enter fullscreen mode Exit fullscreen mode

If logged in:

Output:

Ok("Ashwani")
Enter fullscreen mode Exit fullscreen mode

If not logged in:

Output:

Err("Login required")
🔹 How user id stored after login?

Inside login:

session.insert("username", "Ashwani");
session.insert("user_id", 1);
Enter fullscreen mode Exit fullscreen mode

Now session contains:

username = Ashwani
user_id = 1
Enter fullscreen mode Exit fullscreen mode

✅ 4️⃣

How to get user id from session

let user_id = session.get("user_id");
Enter fullscreen mode Exit fullscreen mode

Example:

let user_id = Some(10);

if let Some(id) = user_id {
    println!("User id {}", id);
}
Enter fullscreen mode Exit fullscreen mode

Output:

User id 10
✅ 5️⃣ What does list_shops_handler do?

Simple steps:

Check login

Get shops from DB

Return JSON
Enter fullscreen mode Exit fullscreen mode

Basic Example

let shops = vec!["Shop1", "Shop2"];

println!("Shops fetched");
Enter fullscreen mode Exit fullscreen mode

Output:

Shops fetched

Returned JSON:

{
  "message": "Shops fetched",
  "data": ["Shop1", "Shop2"]
}
Enter fullscreen mode Exit fullscreen mode

✅ 6️⃣

(File Upload example from your code)

Loop through uploaded files:

let files = vec!["a.jpg", "b.jpg"];

for file in files {
    println!("Saved {}", file);
}
Enter fullscreen mode Exit fullscreen mode

Output:

Saved a.jpg
Saved b.jpg
Enter fullscreen mode Exit fullscreen mode

✅ 7️⃣

How to store vendor_id as foreign key

User logs in → user_id = 5
Enter fullscreen mode Exit fullscreen mode

Then while creating shop:

let user_id = 5;
let shop = ("My Shop", user_id);
println!("Shop created for vendor {}", user_id);
Enter fullscreen mode Exit fullscreen mode

Output:

Shop created for vendor 5

So vendor_id comes from session, not request body.

✅ 8️⃣

Why use .clone()

Because Rust moves values.

Example without clone:

let name = Some("Shop".to_string());
let new_name = name.unwrap();
// name cannot be used now
Enter fullscreen mode Exit fullscreen mode

With clone:

let name = Some("Shop".to_string());
let new_name = name.clone().unwrap();
println!("{}", name.unwrap());
Enter fullscreen mode Exit fullscreen mode

Output:

Shop

Clone keeps original safe.

✅ 9️⃣

Some and match in login

Example of Some

let user = Some("Ashwani");

if let Some(name) = user {
    println!("User found {}", name);
}
Enter fullscreen mode Exit fullscreen mode

Output:

User found Ashwani
Enter fullscreen mode Exit fullscreen mode

Example of match

let valid = true;
let role = "Admin";

match (valid, role) {
    (true, "Admin") => println!("Login success"),
    _ => println!("Invalid"),
}
Enter fullscreen mode Exit fullscreen mode

Output:

Login success
✅ 10️⃣

Full login flow (auth.rs → common.rs)

Step 1: User logs in
Step 2: Save session:

session.insert("username", "Ashwani");
session.insert("user_id", 1);
Enter fullscreen mode Exit fullscreen mode

Step 3: Later handler checks:

ensure_login(&session)?;
Enter fullscreen mode Exit fullscreen mode

If session has username → OK
If not → Unauthorized

Output when logged in:
Authenticated
Output when not logged in:
Login required

Top comments (0)