| Crates.io | rustmvc |
| lib.rs | rustmvc |
| version | 0.2.2 |
| created_at | 2025-11-07 17:44:30.792431+00 |
| updated_at | 2025-11-10 18:11:30.917849+00 |
| description | A lightweight MVC framework for Rust |
| homepage | https://github.com/lorennnzzoo/rustmvc |
| repository | https://github.com/lorennnzzoo/rustmvc |
| max_upload_size | |
| id | 1921938 |
| size | 95,183 |
A lightweight MVC web framework for Rust, built on top of Actix Web and Askama templates.
rustmvc helps you organize your Rust web applications using the familiar Model–View–Controller pattern while keeping it fast, simple, and extensible.
[dependencies]
actix-web = "4.11.0"
askama = "0.14.0"
mime_guess = "2.0.5"
rustmvc = { path = "./rustmvc" } # adjust path based on your workspace
Below is a minimal example that defines a small MVC app using rustmvc.
use rustmvc::{
Server, ActionResult, RequestContext, HttpMethod, RouteRules, RenderModel, Template,
};
#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
message: String,
}
impl RenderModel for IndexTemplate {
fn render_html(&self) -> Result<String, askama::Error> {
self.render()
}
}
fn home(ctx: RequestContext) -> ActionResult {
println!("Received request for {}", ctx.path);
ActionResult::View(std::sync::Arc::new(IndexTemplate {
message: "Welcome to RustMVC!".into(),
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut server = Server::new();
server.add_route("/", home, HttpMethod::GET, vec![]);
server.start("127.0.0.1:8080").await
}
When you open http://127.0.0.1:8080, it will render the Askama template index.html with your message.
Represents the incoming HTTP request. You receive this as a parameter in every controller action.
pub struct RequestContext {
pub params: HashMap<String, String>,
pub headers: HeaderMap,
pub path: String,
pub body: Vec<u8>,
pub method: HttpMethod,
pub rules: Vec<RouteRules>,
pub user: Option<User>,
}
You can access things like headers, query params, and body easily:
fn submit(ctx: RequestContext) -> ActionResult {
if let Some(token) = ctx.headers.get("Authorization") {
println!("Token: {:?}", token);
}
ActionResult::Ok("Form Submitted".to_string())
}
Every controller returns an ActionResult, which defines the HTTP response.
pub enum ActionResult {
Html(String),
View(ArcRenderModel),
Redirect(String),
File(String),
NotFound,
PayloadTooLarge(String),
UnAuthorized(String),
Forbidden(String),
Ok(String),
BadRequest(String),
}
Examples:
ActionResult::Html("<h1>Hello World</h1>".to_string());
ActionResult::Redirect("/login".to_string());
ActionResult::File("logo.png".to_string());
Acts as the main entry point to your application. It stores the list of registered routes, middlewares, and authentication configuration.
let mut server = Server::new();
Each route maps a path and HTTP method to an action function.
server.add_route("/users", list_users, HttpMethod::GET, vec![RouteRules::AllowAnonymous]);
server.add_route("/upload", upload_file, HttpMethod::POST, vec![RouteRules::RequestSizeLimit(1024 * 1024)]);
server.start("127.0.0.1:8080").await?;
The server automatically matches routes, applies middlewares, and handles results.
Middlewares are executed in order before the controller action runs. They can modify the request, log activity, or even return responses directly.
server.add_middleware(|ctx, next| {
println!("Middleware: {}", ctx.path);
let res = next(ctx);
println!("After response");
res
});
You can stack multiple middlewares for logging, authentication, etc. For example, you could log timing or enforce a global header.
Rules allow attaching security or validation behavior to individual routes.
pub enum RouteRules {
Authorize,
AllowAnonymous,
Roles(Vec<String>),
RequestSizeLimit(usize),
}
Usage examples:
// Public route
server.add_route("/", home, HttpMethod::GET, vec![RouteRules::AllowAnonymous]);
// Restricted by payload size
server.add_route(
"/upload",
upload_action,
HttpMethod::POST,
vec![RouteRules::RequestSizeLimit(1024 * 1024)], // 1 MB
);
Your view models (Askama templates) must implement the RenderModel trait.
pub trait RenderModel: Send + Sync {
fn render_html(&self) -> Result<String, askama::Error>;
}
Example with Askama:
#[derive(Template)]
#[template(path = "user.html")]
struct UserTemplate {
name: String,
}
impl RenderModel for UserTemplate {
fn render_html(&self) -> Result<String, askama::Error> {
self.render()
}
}
fn show_user(_: RequestContext) -> ActionResult {
ActionResult::View(std::sync::Arc::new(UserTemplate {
name: "Lorenzo".to_string(),
}))
}
The server supports JWT-based authentication via an AuthConfig that can generate and validate tokens.
This part can be extended by enabling the commented-out set_auth_config and validation logic.
Example token generation:
let claims = Claims {
sub: "user123".into(),
roles: vec!["admin".into()],
};
let token = server.generate_token(claims, 3600);
Static files (like assets) are served from the wwwroot directory automatically when returned via ActionResult::File.
fn show_logo(_: RequestContext) -> ActionResult {
ActionResult::File("images/logo.png".into())
}
If you register:
server.add_middleware(logging_middleware);
server.add_middleware(auth_middleware);
For any request, execution will be:
logging_middleware -> auth_middleware -> route_action -> response
This composition pattern makes feature layering (e.g., auth, logging, request limits) simple and modular.
project/
│
├── src/
│ ├── main.rs
│ ├── controllers/
│ │ └── home.rs
│ └── rustmvc/
│ ├── mod.rs
│ └── authentication.rs
│
├── templates/
│ └── index.html
│
└── wwwroot/
├── css/
├── js/
└── images/
RustMVC is ideal for:
If you already use Actix Web and want structured controllers, type-safe views, and rule-based routing, RustMVC provides a great foundation.