Rust web framework for APIs
So simple it feels like cheating.
Auth, validation, error handling, OpenAPI — all built in with sane defaults. You write handlers, Rapina handles the rest.
cargo install rapina-cliuse rapina::prelude::*;
#[public]
#[post("/login")]
async fn login(
body: Validated<Json<LoginRequest>>,
) -> Result<Json<TokenResponse>> {
let user = authenticate(body.into_inner()).await?;
Ok(Json(issue_token(&user)?))
}
// Protected by default — no #[public] means JWT required
#[get("/users/:id")]
async fn get_user(
id: Path<UserId>,
db: State<Database>,
) -> Result<Json<UserResponse>> {
let user = db.find_by_id(*id).await?;
Ok(Json(user.into()))
}Secure Unless You Say Otherwise
Routes require auth by default. Want a public endpoint? Add #[public]. That's it. You opt out of security, never opt in.
Decisions Already Made
Error format, validation responses, project structure, trace IDs — all decided for you. Override when you need to. You won't need to often.
LLMs Get It
Predictable conventions mean AI tools generate correct Rapina code on the first try. Consistent patterns, reliable completions.
Just Add #[public]
Every route is authenticated. Write your handler, access the user's claims directly from the function signature. Need a public endpoint? One attribute. No middleware chains, no guard configs, no forgetting to protect a route.
#[get("/me")]
async fn me(claims: Claims) -> Result<Json<User>> {
// claims.sub is the authenticated user
Ok(Json(find_user(claims.sub).await?))
}
#[public]
#[get("/health")]
async fn health() -> &'static str {
"ok"
}Validate, Don't Pray
Derive your validation rules on the struct. Rapina rejects bad requests with a structured 422 before your handler even runs. Every error comes with a trace_id so you can find it in production in seconds.
#[derive(Deserialize, Validate, JsonSchema)]
struct CreateUser {
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
}
#[post("/users")]
async fn create(
body: Validated<Json<CreateUser>>,
) -> Result<Json<User>> {
// body is already validated here
Ok(Json(save(body.into_inner()).await?))
}One Command Away
Scaffold, run, diagnose, inspect. The CLI does the boring parts so you write code from minute one. rapina doctor catches misconfigurations before they become production incidents.
$ rapina new my-app
Done! cd my-app && rapina dev
$ rapina dev
Listening on http://localhost:3000
$ rapina doctor
[ok] Database connection
[ok] JWT secret configured
[ok] Migrations up to date
$ rapina routes
GET /v1/users list_users
POST /v1/users create_user
GET /health health (public)Errors You Can Actually Debug
Every response follows one envelope. Every request gets a trace_id. When your on-call gets paged at 3am, they grep one ID and find the exact request. No more guessing.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "invalid email" },
{ "field": "password", "message": "min 8 chars" }
]
},
"trace_id": "550e8400-e29b-41d4-a716"
}Batteries Included
Everything you need to ship an API. Nothing you don't.
Built on the most admired language in the world
Rust's performance. None of the complexity.
Rapina gives you what Rust is known for — without making you fight the language to build a web app.
Abstractions
Extractors, middleware, validation — all resolved at compile time. No runtime reflection, no hidden allocations.
By default
No garbage collector, no null pointer exceptions, no data races. The compiler catches what other languages find in production.
Footprint
Single binary, no runtime dependencies. Deploy a 10MB container that starts in milliseconds and handles thousands of concurrent connections.
Frequently Asked Questions
Axum and Actix are great low-level building blocks. Rapina is a framework. Auth, validation, error envelopes, OpenAPI, project structure — you get all of that out of the box instead of wiring it together yourself for every project. Built directly on Hyper for the same performance, with opinions that let you ship instead of configure.
The core is stable and well-tested. We're actively shipping features and growing the contributor base toward 1.0. Breaking changes are taken seriously and communicated clearly — check the changelog for current status.
Everything is overridable. Custom error formats, custom auth providers, different validation rules — escape hatches exist for all of it. The defaults are there so you move fast when you don't need customization, which is most of the time.
No. If you can write a function that takes arguments and returns a value, you can build an API with Rapina. The framework handles the Rust-specific complexity — lifetimes, async wiring, type gymnastics — so you focus on your domain logic.