As a beginner, I found myself struggling to grasp existing frameworks like actix-web and Rocket. When I attempted to rewrite a Go web service in Rust, every framework seemed more complex than those in Go. Rust already has a steep learning curve—why make web frameworks even more convoluted?
When Tokio released Axum, I was thrilled, thinking I could finally stop maintaining my own web framework. However, in practice, Axum's apparent simplicity masked excessive type gymnastics and generic definitions. Writing even a simple middleware required deep Rust expertise and verbose, obscure boilerplate code.
Thus, I decided to continue maintaining my own framework—one that's intuitive, feature-rich, and beginner-friendly.
Salvo is simple yet powerful, arguably the most capable in the Rust ecosystem. Despite its strength, it remains easy to learn and use, sparing you the pain of unnecessary complexity.
Hyper handles much of the low-level work, making it a solid foundation. Salvo builds on this with a powerful, flexible routing system and essential features like Acme, OpenAPI, and JWT Auth.
Salvo unifies Handlers and Middleware: Middleware is a Handler, attached via hoop
to a Router
. Both process Request
and may write to Response
. Handlers take three parameters:
Request
: The incoming request.Depot
: Temporary data storage during processing.Response
: The outgoing response.For brevity, unused parameters can be omitted or reordered:
The routing API is equally simple yet powerful. Typically, you only need to focus on the Router
type.
Salvo also automates OpenAPI documentation, parameter extraction, and error handling. Writing handlers feels as natural as writing plain functions. Here’s an example:
Here, JsonBody<CreateOrUpdateMessageLog>
automatically parses JSON (supporting nested types and multi-source data), while #[endpoint]
generates OpenAPI docs and streamlines error handling.
Salvo’s routing stands apart. Router
s can be flat or nested into trees, decoupling business logic trees from URL paths.
A typical setup:
Public routes (e.g., listing/viewing articles) and private routes (e.g., editing/deleting) can be grouped separately, with middleware enforcing auth:
Both can coexist under a parent router, forming this structure:
{id}
matches a path segment. To restrict it to digits, use a regex: r"{id:/\d+/}"
.