| Crates.io | rapina |
| lib.rs | rapina |
| version | 0.2.0 |
| created_at | 2026-01-21 04:12:05.987055+00 |
| updated_at | 2026-01-25 02:14:16.558818+00 |
| description | A fast, type-safe web framework for Rust inspired by FastAPI |
| homepage | |
| repository | https://github.com/arferreira/rapina |
| max_upload_size | |
| id | 2058265 |
| size | 272,237 |
Predictable, auditable, and secure APIs — written by humans, accelerated by AI.
Rapina is a web framework for Rust inspired by FastAPI, focused on productivity, type safety, and clear conventions.
cargo install rapina-cli
rapina new my-app
cd my-app
rapina dev
Or add to an existing project:
[dependencies]
rapina = "0.2.0"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
use rapina::prelude::*;
#[get("/")]
async fn hello() -> &'static str {
"Hello, Rapina!"
}
#[get("/users/:id")]
async fn get_user(id: Path<u64>) -> String {
format!("User ID: {}", id.into_inner())
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let router = Router::new()
.get("/", hello)
.get("/users/:id", get_user);
Rapina::new()
.router(router)
.listen("127.0.0.1:3000")
.await
}
| Principle | Description |
|---|---|
| Opinionated | Convention over configuration. Clear defaults, escape hatches when needed. |
| Type-safe | Typed extractors, typed errors, everything checked at compile time. |
| AI-friendly | Predictable patterns that humans and LLMs understand equally well. |
| Production-ready | Standardized errors with trace_id, JWT auth, observability built-in. |
Clean, type-safe parameter extraction:
#[get("/users/:id")]
async fn get_user(id: Path<u64>) -> Result<Json<User>> {
let user = find_user(id.into_inner()).await?;
Ok(Json(user))
}
#[post("/users")]
async fn create_user(body: Json<CreateUser>) -> Result<Json<User>> {
let user = save_user(body.into_inner()).await?;
Ok(Json(user))
}
#[get("/search")]
async fn search(query: Query<SearchParams>) -> Json<Vec<Item>> {
let results = search_items(&query).await;
Json(results)
}
Available extractors: Path, Json, Query, Form, Headers, State, CurrentUser
Type-safe configuration with fail-fast validation:
#[derive(Config)]
struct Settings {
#[env = "DATABASE_URL"]
database_url: String,
#[env = "PORT"]
#[default = "3000"]
port: u16,
}
fn main() {
load_dotenv();
let config = Settings::from_env().expect("Missing config");
}
Protected by default — all routes require JWT unless marked #[public]:
#[public]
#[post("/login")]
async fn login(body: Json<LoginRequest>, auth: State<AuthConfig>) -> Result<Json<TokenResponse>> {
let token = auth.create_token(&body.username)?;
Ok(Json(TokenResponse::new(token, auth.expiration())))
}
#[get("/me")]
async fn me(user: CurrentUser) -> Json<UserResponse> {
Json(UserResponse { id: user.id })
}
Rapina::new()
.with_auth(AuthConfig::from_env()?)
.public_route("POST", "/login")
.router(router)
.listen("127.0.0.1:3000")
.await
Every error includes a trace_id for debugging:
{
"error": { "code": "NOT_FOUND", "message": "user not found" },
"trace_id": "550e8400-e29b-41d4-a716-446655440000"
}
Error::bad_request("invalid input") // 400
Error::unauthorized("login required") // 401
Error::not_found("user not found") // 404
Error::validation("invalid email") // 422
Error::internal("something went wrong") // 500
Automatic OpenAPI 3.0 generation with CLI tools:
rapina openapi export -o openapi.json # Export spec
rapina openapi check # Verify spec matches code
rapina openapi diff --base main # Detect breaking changes
rapina new my-app # Create new project
rapina dev # Dev server with hot reload
rapina routes # List all routes
rapina doctor # Health checks
Full documentation available at userapina.com
Rapina is opinionated by design: a clear happy path, with escape hatches when needed.
| Principle | Description |
|---|---|
| Predictability | Clear conventions, obvious structure |
| Auditability | Typed contracts, traceable errors |
| Security | Protected by default, guard rails built-in |
| AI-friendly | Patterns that LLMs can understand and generate |
MIT