Salvo is a Rust web framework focused on simplicity and productivity. It features automatic OpenAPI documentation, built-in ACME (Let's Encrypt) TLS, and a middleware system that makes Rust web development feel effortless.
Why Salvo Matters
Axum is powerful but requires assembling many pieces. Salvo includes OpenAPI generation, TLS certificates, WebSocket support, and rate limiting out of the box — with less boilerplate.
What you get for free:
- Automatic OpenAPI 3.0 documentation generation
- Built-in ACME TLS (automatic Let's Encrypt certificates)
- WebSocket support
- Rate limiting middleware
- CORS, logging, compression built in
- Proxy and static file serving
- Less boilerplate than Axum for common tasks
Quick Start
cargo new my-api
cd my-api
# Add to Cargo.toml:
# salvo = { version = "0.70", features = ["oapi"] }
# tokio = { version = "1", features = ["full"] }
cargo run
Hello World
use salvo::prelude::*;
#[handler]
async fn hello(res: &mut Response) {
res.render(Text::Plain("Hello from Salvo!"));
}
#[tokio::main]
async fn main() {
let router = Router::new().get(hello);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
REST API with OpenAPI
use salvo::prelude::*;
use salvo::oapi::extract::*;
#[derive(Serialize, Deserialize, ToSchema)]
struct User {
id: u64,
name: String,
email: String,
}
#[derive(Deserialize, ToSchema)]
struct CreateUser {
name: String,
email: String,
}
#[endpoint(
tags("Users"),
summary = "List all users",
responses(
(status_code = 200, description = "Success", body = Vec<User>)
)
)]
async fn list_users(res: &mut Response) {
let users = vec![
User { id: 1, name: "Alice".into(), email: "alice@example.com".into() },
User { id: 2, name: "Bob".into(), email: "bob@example.com".into() },
];
res.render(Json(users));
}
#[endpoint(
tags("Users"),
summary = "Create a user",
)]
async fn create_user(
body: JsonBody<CreateUser>,
res: &mut Response,
) {
let user = User {
id: 3,
name: body.name.clone(),
email: body.email.clone(),
};
res.render(Json(user));
}
#[tokio::main]
async fn main() {
let router = Router::new()
.push(Router::with_path("users")
.get(list_users)
.post(create_user)
);
// Auto-generate OpenAPI docs at /api-doc
let doc = OpenApi::new("My API", "1.0.0").merge_router(&router);
let router = router
.push(doc.into_router("/api-doc/openapi.json"))
.push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Automatic HTTPS (ACME/Let's Encrypt)
use salvo::prelude::*;
use salvo::conn::rustls_acme::AcmeListener;
#[tokio::main]
async fn main() {
let router = Router::new().get(hello);
// Automatic TLS certificate from Let's Encrypt!
let acceptor = AcmeListener::builder()
.domain("myapp.example.com")
.cache_path("./certs")
.bind("0.0.0.0:443")
.await;
Server::new(acceptor).serve(router).await;
}
Middleware
use salvo::prelude::*;
use salvo::rate_limiter::*;
use salvo::cors::Cors;
use salvo::logging::Logger;
use salvo::compression::Compression;
#[tokio::main]
async fn main() {
let cors = Cors::new()
.allow_origin("https://myapp.com")
.allow_methods(vec!["GET", "POST"])
.into_handler();
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(10), // 10 req/sec per IP
);
let router = Router::new()
.hoop(Logger::new())
.hoop(Compression::new().min_length(1024))
.hoop(cors)
.hoop(limiter)
.get(hello);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Useful Links
Building Rust web services? Check out my developer tools on Apify for ready-made web scrapers, or email spinov001@gmail.com for custom solutions.
Top comments (0)