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
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
It connects:
Client → Route → Handler → DB → Response
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
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)
✅ 2. Authenticate User
ensure_login(&session)?;
✅ 3. Call Database Functions
list_brand_models(&state.pool).await
✅ 4. Format Response
HttpResponse::Ok().json(response)
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> {
🔹 Step 1: Extract dependencies
state → DB pool access
session → check login
🔹 Step 2: Authenticate
ensure_login(&session)?;
If not logged in → returns 401 Unauthorized.
🔹 Step 3: Call DB layer
let items = list_brand_models(&state.pool)
.await
.map_err(error::ErrorInternalServerError)?;
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,
};
🔹 Step 5: Return JSON
Ok(HttpResponse::Ok().json(response))
🧠 Final Flow
GET /api/brand-models
↓
list_brand_models_handler
↓
ensure_login
↓
db::list_brand_models
↓
JSON response
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> {
🔹 Extract JSON body
body: web::Json
User sends:
{
"name": "Toyota"
}
Actix automatically converts JSON into BrandModelForm.
🔹 Authenticate
ensure_login(&session)?;
🔹 Call DB function
create_brand_model(&state.pool, &body.into_inner())
.await
🔹 Return success response
Ok(HttpResponse::Created().json(response))
Example 3️⃣: File Upload Handler
From your project:
pub async fn upload_brand_model_images_handler(
mut payload: Multipart,
session: Session,
)
Purpose:
Accept file upload
Stream chunks
Save file to disk
Loop through uploaded files:
while let Some(item) = payload.next().await {
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
);
This prevents file name conflicts.
Save file:
file.write_all(&data).await?;
Return JSON:
Ok(HttpResponse::Ok().json(response))
5️⃣ Authentication Handler Example (Login)
From your project:
pub async fn login(
body: web::Json<LoginForm>,
state: web::Data<AppState>,
session: Session,
)
Step 1: Find user in DB
let maybe_user = find_user_by_username(&state.pool, &body.username).await?;
Returns:
Some(user) if found
None if not found
Step 2: Check credentials
if let Some(user) = maybe_user {
Step 3: Store session
session.insert("username", user.username.clone())?;
session.insert("user_id", user.id)?;
session.insert("role", user.role.clone())?;
Now user is logged in.
Step 4: Return response
Ok(HttpResponse::Ok().json(response))
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
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
Handlers act as:
Traffic controller
Security guard
Request manager
Response builder
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.
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);
}
Output
App name is My App
In your project:
state.pool → DB connection
session – Login information
Why?
To check if user is logged in.
Example
let username = Some("Ashwani");
if let Some(name) = username {
println!("Welcome {}", name);
}
Output
Welcome Ashwani
In real app:
session.get("username")
🔹 (C) body – JSON data from request
Why?
When user sends data like:
{
"name": "Toyota"
}
Example
struct Car {
name: String,
}
let body = Car { name: "Toyota".to_string() };
println!("{}", body.name);
Output
Toyota
🔹 (D) path – ID from URL
URL:
/shops/5
Example
let path_id = 5;
println!("Shop id is {}", path_id);
Output
Shop id is 5
🔹 (E) Multipart – File Upload
When user uploads file.
Example idea
let file_name = "image.jpg";
println!("Saving file {}", file_name);
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))
}
=====================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))
}
pub async fn list_brand_models_handler(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse>
Because this handler calls:
list_brand_models(&state.pool).await
✅ 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)?;
✅ 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>,
...
)
✅ 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>, ...)
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,
)
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);
Output:
5
🔹 body.into_inner()
Unwrap JSON.
let body = "Toyota";
let value = body;
println!("{}", value);
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())
}
If logged in:
Output:
Ok("Ashwani")
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);
Now session contains:
username = Ashwani
user_id = 1
✅ 4️⃣
How to get user id from session
let user_id = session.get("user_id");
Example:
let user_id = Some(10);
if let Some(id) = user_id {
println!("User id {}", id);
}
Output:
User id 10
✅ 5️⃣ What does list_shops_handler do?
Simple steps:
Check login
Get shops from DB
Return JSON
Basic Example
let shops = vec!["Shop1", "Shop2"];
println!("Shops fetched");
Output:
Shops fetched
Returned JSON:
{
"message": "Shops fetched",
"data": ["Shop1", "Shop2"]
}
✅ 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);
}
Output:
Saved a.jpg
Saved b.jpg
✅ 7️⃣
How to store vendor_id as foreign key
User logs in → user_id = 5
Then while creating shop:
let user_id = 5;
let shop = ("My Shop", user_id);
println!("Shop created for vendor {}", user_id);
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
With clone:
let name = Some("Shop".to_string());
let new_name = name.clone().unwrap();
println!("{}", name.unwrap());
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);
}
Output:
User found Ashwani
Example of match
let valid = true;
let role = "Admin";
match (valid, role) {
(true, "Admin") => println!("Login success"),
_ => println!("Invalid"),
}
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);
Step 3: Later handler checks:
ensure_login(&session)?;
If session has username → OK
If not → Unauthorized
Output when logged in:
Authenticated
Output when not logged in:
Login required
Top comments (0)