Request & Response
The Request Object
Every handler receives a Request object:
#![allow(unused)] fn main() { #[handler] pub async fn show(req: Request) -> Response { // Use request data } }
Accessing Request Data
#![allow(unused)] fn main() { // Path: /users/123 let id = req.param::<i64>("id")?; // Query: /users?page=2&sort=name let page = req.query::<i64>("page").unwrap_or(1); let sort = req.query::<String>("sort"); // Headers let auth = req.header("Authorization"); let content_type = req.content_type(); // Method and path let method = req.method(); let path = req.path(); let full_url = req.url(); }
Request Body
#![allow(unused)] fn main() { // JSON body let data: CreateUserData = req.json().await?; // Form data let form: HashMap<String, String> = req.form().await?; // Raw body let bytes = req.body_bytes().await?; let text = req.body_string().await?; }
File Uploads
#![allow(unused)] fn main() { let file = req.file("avatar").await?; // File properties let filename = file.filename(); let content_type = file.content_type(); let size = file.size(); // Save file file.store("avatars").await?; // or file.store_as("avatars", "custom-name.jpg").await?; }
Authentication
#![allow(unused)] fn main() { // Check if authenticated if req.is_authenticated() { let user = req.user()?; println!("Hello, {}", user.name); } // Get optional user if let Some(user) = req.user_optional() { // ... } }
Session Data
#![allow(unused)] fn main() { // Read session let user_id = req.session().get::<i64>("user_id"); // Write session (needs mutable access) req.session_mut().set("flash", "Welcome back!"); // Flash messages let flash = req.session().flash("message"); }
Cookies
#![allow(unused)] fn main() { // Read cookie let token = req.cookie("remember_token"); // Cookies are typically set on responses }
Database Access
#![allow(unused)] fn main() { let db = req.db(); let users = User::find().all(db).await?; }
Responses
Handlers return Response which is Result<HttpResponse, HttpResponse>:
#![allow(unused)] fn main() { pub type Response = Result<HttpResponse, HttpResponse>; }
JSON Responses
#![allow(unused)] fn main() { #[handler] pub async fn index(req: Request) -> Response { Ok(json!({ "users": users, "total": 100 })) } // Or using HttpResponse directly Ok(HttpResponse::json(data)) }
HTML Responses
#![allow(unused)] fn main() { Ok(HttpResponse::html("<h1>Hello</h1>")) }
Text Responses
#![allow(unused)] fn main() { Ok(HttpResponse::text("Hello, World!")) }
Inertia Responses
#![allow(unused)] fn main() { // Basic Inertia response Inertia::render(&req, "Users/Index", props) // With saved context (for form handlers) let ctx = SavedInertiaContext::from(&req); let form = req.input().await?; // Consumes request Inertia::render_ctx(&ctx, "Users/Form", props) }
See Controllers - Form Handling for complete examples.
Redirects
#![allow(unused)] fn main() { // Simple redirect Ok(Redirect::to("/dashboard")) // Redirect back Ok(Redirect::back(&req)) // Redirect with flash message Ok(Redirect::to("/users").with("success", "User created!")) // Named route redirect Ok(Redirect::route("users.show", [("id", "1")])) }
Status Codes
#![allow(unused)] fn main() { // Success codes Ok(HttpResponse::ok(body)) Ok(HttpResponse::created(body)) Ok(HttpResponse::no_content()) // Error codes Err(HttpResponse::bad_request("Invalid input")) Err(HttpResponse::unauthorized("Please login")) Err(HttpResponse::forbidden("Access denied")) Err(HttpResponse::not_found("User not found")) Err(HttpResponse::server_error("Something went wrong")) }
Custom Status
#![allow(unused)] fn main() { Ok(HttpResponse::new(StatusCode::ACCEPTED) .json(data)) }
Response Headers
#![allow(unused)] fn main() { Ok(HttpResponse::json(data) .header("X-Custom", "value") .header("Cache-Control", "no-cache")) }
Cookies
#![allow(unused)] fn main() { Ok(HttpResponse::json(data) .cookie(Cookie::new("token", "abc123")) .cookie(Cookie::new("remember", "true") .http_only(true) .secure(true) .max_age(Duration::days(30)))) }
File Downloads
#![allow(unused)] fn main() { // Download file Ok(HttpResponse::download("path/to/file.pdf", "report.pdf")) // Stream file Ok(HttpResponse::file("path/to/video.mp4")) }
Error Handling
Return errors as HttpResponse:
#![allow(unused)] fn main() { #[handler] pub async fn show(req: Request, id: i64) -> Response { let user = User::find_by_id(id) .one(&req.db()) .await? .ok_or_else(|| HttpResponse::not_found("User not found"))?; Ok(json!(user)) } }
Domain Errors
Use #[domain_error] for typed errors:
#![allow(unused)] fn main() { #[domain_error] pub enum UserError { #[error("User not found")] #[status(404)] NotFound, #[error("Email already taken")] #[status(409)] EmailTaken, } #[handler] pub async fn store(req: Request) -> Response { let result = create_user().await?; // ? converts UserError to HttpResponse Ok(json!(result)) } }
Form Requests
For automatic validation:
#![allow(unused)] fn main() { #[derive(FormRequest)] pub struct CreateUserRequest { #[validate(required, email)] pub email: String, #[validate(required, min(8))] pub password: String, #[validate(same("password"))] pub password_confirmation: String, } #[handler] pub async fn store(req: Request, form: CreateUserRequest) -> Response { // form is validated, safe to use Ok(json!({"email": form.email})) } }
If validation fails, Ferro automatically returns a 422 response with errors.