Response Object
The Res type is a response builder that uses interior mutability for flexible response construction.
Response Flow
- Use
set_*methods to configure the response (headers, cookies, status) - Call a finalizer method to return the response
Finalizer Methods
HTML Response
Return server-rendered HTML:
use rejoice::{Req, Res, html};
pub async fn get(req: Req, res: Res) -> Res {
res.html(html! {
h1 { "Hello, World!" }
})
}
Returns: 200 OK with Content-Type: text/html; charset=utf-8
JSON Response
Return JSON data:
use rejoice::{Req, Res, json};
use serde::Serialize;
#[derive(Serialize)]
struct User {
id: i32,
name: String,
}
pub async fn get(req: Req, res: Res) -> Res {
let user = User { id: 1, name: "Alice".into() };
res.json(&user)
}
// Or with the json! macro:
pub async fn get(req: Req, res: Res) -> Res {
res.json(&json!({
"status": "ok",
"count": 42
}))
}
Returns: 200 OK with Content-Type: application/json
Redirect
Redirect to another URL:
pub async fn get(req: Req, res: Res) -> Res {
// Temporary redirect (302 Found)
res.redirect("/login")
}
pub async fn get(req: Req, res: Res) -> Res {
// Permanent redirect (301 Moved Permanently)
res.redirect_permanent("/new-url")
}
Raw Response
Return raw bytes with custom content type:
pub async fn get(req: Req, res: Res) -> Res {
let pdf_bytes = get_pdf_data();
res.set_header("Content-Type", "application/pdf")
.set_header("Content-Disposition", "attachment; filename=\"report.pdf\"")
.raw(pdf_bytes)
}
Error Helpers
Convenient methods for common HTTP error responses:
// 400 Bad Request
res.bad_request("Invalid form data")
// 401 Unauthorized
res.unauthorized("Please log in")
// 403 Forbidden
res.forbidden("Access denied")
// 404 Not Found
res.not_found("Page not found")
// 500 Internal Server Error
res.internal_error("Something went wrong")
Each returns an HTML response with the appropriate status code.
Example
use rejoice::{Req, Res};
use serde::Deserialize;
#[derive(Deserialize)]
struct LoginForm {
email: String,
password: String,
}
pub async fn post(req: Req, res: Res) -> Res {
let Ok(form) = req.body.as_form::<LoginForm>() else {
return res.bad_request("Invalid form data");
};
if form.password.len() < 8 {
return res.bad_request("Password too short");
}
// Process login...
res.redirect("/dashboard")
}
Setting Headers
Add custom headers to the response:
pub async fn get(req: Req, res: Res) -> Res {
res.set_header("X-Custom-Header", "value")
.set_header("Cache-Control", "max-age=3600")
.html(html! { h1 { "Hello!" } })
}
Setting Status Code
Override the default status code:
use axum::http::StatusCode;
pub async fn get(req: Req, res: Res) -> Res {
res.set_status(StatusCode::CREATED)
.json(&json!({ "id": 123 }))
}
Setting Cookies
Simple Cookie
pub async fn get(req: Req, res: Res) -> Res {
res.set_cookie("visited", "true")
.html(html! { h1 { "Welcome!" } })
}
Cookie with Options
pub async fn post(req: Req, res: Res) -> Res {
res.set_cookie_with_options(
"session_id", // name
"abc123", // value
Some("/"), // path
Some(3600), // max_age (seconds)
true, // http_only
true, // secure
Some("Strict"), // same_site
)
.redirect("/dashboard")
}
Delete Cookie
pub async fn post(req: Req, res: Res) -> Res {
res.delete_cookie("session_id")
.redirect("/")
}
Chaining Methods
All set_* methods return &Res and can be chained:
pub async fn get(req: Req, res: Res) -> Res {
res.set_cookie("last_visit", "2025-01-01")
.set_header("X-Frame-Options", "DENY")
.set_header("Cache-Control", "no-cache")
.html(html! { h1 { "Secured page" } })
}
Cookies Persist Across Branches
Cookies set on res apply to all subsequent responses:
pub async fn get(req: Req, res: Res) -> Res {
// This cookie is set regardless of which branch executes
res.set_cookie("last_visit", "2025-01-01");
if !is_authenticated(&req) {
// Redirect gets the "last_visit" cookie
return res.redirect("/login");
}
// HTML also gets the "last_visit" cookie
res.html(html! { h1 { "Dashboard" } })
}
Next Steps
- Request Object - Read incoming request data
- Layouts - Redirects bypass layouts
- Database - Query data for responses