Rapina 0.9.0 shipped on March 6, 2026. This was the largest release to date, centered around real-time features — raw WebSocket support and a higher-level Relay system for topic-based push. It also added OpenAPI import, starter templates, and an interactive tutorial. The project moved to the rapina-rs GitHub org in this release.
WebSocket support
Raw WebSocket support was added behind the websocket feature flag. The WebSocketUpgrade extractor handled the HTTP upgrade handshake; WebSocket gave direct access to the bidirectional connection.
Enable the feature:
[dependencies]
rapina = { version = "0.9", features = ["websocket"] }A simple echo handler:
use rapina::prelude::*;
use rapina::websocket::{WebSocketUpgrade, Message};
#[get("/ws")]
#[public]
async fn ws(upgrade: WebSocketUpgrade) -> impl IntoResponse {
upgrade.on_upgrade(|mut socket| async move {
while let Some(Ok(msg)) = socket.recv().await {
if msg.is_text() || msg.is_binary() {
socket.send(msg).await.ok();
}
}
})
}on_upgrade spawned a task for the connection and returned the 101 Switching Protocols response immediately.
Relay system
The Relay system added topic-based push over WebSocket without requiring handlers to manage connections directly. Any handler could push a message to a topic; connected clients received it through a shared WebSocket endpoint.
Setup
use rapina::relay::RelayConfig;
use rapina::prelude::*;
Rapina::new()
.with_relay(RelayConfig::default())
.discover()
.listen("127.0.0.1:3000")
.awaitRelayConfig::default() registered a WebSocket endpoint at /ws with a broadcast buffer of 128 messages per topic and a maximum of 50 subscriptions per connection.
Pushing from handlers
The Relay extractor was available in any handler. relay.push() delivered a message to all clients subscribed to the given topic:
use rapina::relay::Relay;
use rapina::prelude::*;
#[post("/orders")]
async fn create_order(relay: Relay, body: Json<NewOrder>) -> Result<Json<Order>> {
let order = save_order(&body).await?;
relay.push("orders:new", "created", &order).await?;
Ok(Json(order))
}Channel handlers
The #[relay("pattern")] attribute registered a server-side handler that received RelayEvent callbacks for subscribe, message, and disconnect events. Patterns supported exact matches and prefix wildcards:
use rapina::relay::{Relay, RelayEvent};
use rapina::prelude::*;
#[relay("room:*")]
async fn room_channel(event: RelayEvent, relay: Relay) -> Result<()> {
match &event {
RelayEvent::Join { topic, conn_id } => {
relay.track(topic, *conn_id, serde_json::json!({}));
}
RelayEvent::Message { topic, event: ev, payload, .. } => {
relay.push(topic, ev, payload).await?;
}
RelayEvent::Leave { topic, conn_id } => {
relay.untrack(topic, *conn_id);
}
}
Ok(())
}"room:*" matched any topic starting with "room:". Exact patterns took priority over prefix patterns during dispatch.
Presence tracking
relay.track() and relay.untrack() maintained a presence map per topic. relay.presence(topic) returned the current list of tracked connections with their associated metadata.
rapina import openapi
The rapina import openapi command scaffolded handlers, request/response types, and routes from an existing OpenAPI spec. It supported JSON and YAML files and required the import-openapi feature:
# Generate handlers from a spec file
rapina import openapi api.yaml
# Preview without writing files
rapina import openapi api.yaml --dry-run
# Filter by tag
rapina import openapi api.yaml --tags users,ordersStarter templates
rapina new gained a --template flag with three options:
# Default REST API scaffold (no flag needed)
rapina new my-app
# CRUD template — resource model, SeaORM entity, full handler set
rapina new my-app --template crud
# Auth template — JWT login, protected routes, CurrentUser extractor wired up
rapina new my-app --template authEach template generated a fully working project with Cargo.toml, migrations, and example handlers.
group parameter in route macros
Route macros gained a group parameter for prefixing routes during auto-discovery. Routes registered via discover() were grouped under the specified prefix without changing their handler path:
#[get("/users", group = "/api/v1")]
async fn list_users() -> Json<Vec<User>> { ... }
#[get("/users/:id", group = "/api/v1")]
async fn get_user(id: Path<u64>) -> Json<User> { ... }The above registered GET /api/v1/users and GET /api/v1/users/:id.
Interactive tutorial
A six-chapter interactive tutorial was added to the docs site. It covered routing, extractors, database integration, authentication, testing, and deployment, walking through a complete application from scratch.
Bug fixes and improvements
Graceful shutdown on Windows (#304)
Graceful shutdown previously relied on Unix signal handling and did not work on Windows. This was fixed by adding a Windows-compatible shutdown path using the windows crate for Ctrl+C interception alongside the existing Unix SIGTERM/SIGINT handling.
OpenAPI commands accept --host and --port
rapina openapi export, rapina openapi check, and rapina openapi diff gained --host and --port flags. Previously the commands always fetched the spec from http://127.0.0.1:3000. Now they can target any address:
rapina openapi export --host 0.0.0.0 --port 8080Docs
New documentation pages were added for WebSocket, Relay, OpenAPI, Testing, Validation, and Migrations.
rapina-rs org
All repository references were updated to the new rapina-rs GitHub organization.
Upgrade by bumping the version in your Cargo.toml:
rapina = "0.9"