ESC
Type to search...

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.

Get Started
cargo install rapina-cli
use 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.

Zero-cost

Abstractions

Extractors, middleware, validation — all resolved at compile time. No runtime reflection, no hidden allocations.

Memory safe

By default

No garbage collector, no null pointer exceptions, no data races. The compiler catches what other languages find in production.

Tiny

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.

Rapina

The framework that makes you want to ship every day

AuthProtected by default
ValidationType-safe & structured
OpenAPIAuto-generated
DeploySingle binary