⚡ A · R · V · I · K — Async Rust Velocity Integration Kit
Built on Tokio + Hyper. Engineered for maximum performance.
Arvik stands for Async Rust Velocity Integration Kit.
It is a high-performance Rust web framework built from the ground up on Tokio and Hyper 1.x, designed to unify the best features of Axum and Actix-web under one ergonomic, blazing-fast API.
⚡ v0.5.2 — Multipart Polish Arvik now has production-ready multipart uploads with streaming temp-file saves, progress streams, configurable limits, and clearer upload errors. Follow along on YouTube or join the Discord to track progress.
# Clone the repo
git clone https://github.com/AarambhDevHub/arvik.git
cd arvik
# Run the server
cargo run -p arvikThen in another terminal:
curl http://localhost:8080/
# => {"status":"healthy","framework":"Arvik","version":"0.5.2"}
curl http://localhost:8080/users/42
# => {"id":"42","name":"User from path param"}
curl http://localhost:8080/not-a-route
# => 404 Not FoundExtract typed data from requests with compile-time safety. Handlers support up to 16 extractors.
use arvik::{Router, get, post, Json, Path, Query, State};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct SearchParams { q: String, page: Option<u32> }
#[derive(Deserialize)]
struct CreateUser { name: String, email: String }
#[derive(Serialize)]
struct User { id: u32, name: String }
// Path + Query extractors
async fn search(Path(id): Path<u32>, Query(params): Query<SearchParams>) -> String {
format!("User {id} searching: {}", params.q)
}
// JSON body extractor
async fn create_user(Json(body): Json<CreateUser>) -> Json<User> {
Json(User { id: 1, name: body.name })
}| Extractor | Source | Notes |
|---|---|---|
Path<T> |
URL path params | Serde deserialization |
Query<T> |
Query string | Via serde_urlencoded |
Json<T> |
Request body | Validates Content-Type |
Form<T> |
Request body | URL-encoded forms |
State<S> |
Router state | Shared app state |
TypedHeader<T> |
Request headers | Via headers crate |
Extension<T> |
Extensions map | Middleware data |
MatchedPath |
Router | Route pattern |
ConnectInfo<T> |
Connection | Client address |
Multipart |
Request body | File uploads |
Method / Uri / Version |
Request | HTTP metadata |
Bytes / String / Body |
Request body | Raw access |
Zero-allocation request matching, dynamic path parameters, and catch-all wildcards.
use arvik::{Router, get, post, Json, Path};
async fn get_user(Path(id): Path<u32>) -> Json<serde_json::Value> {
Json(serde_json::json!({ "user_id": id }))
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Home" }))
.route("/users/{id}", get(get_user))
.route("/files/{*path}", get(|| async { "File content" }));
arvik::serve_app("0.0.0.0:8080", app).await.unwrap();
}Seamlessly nest routers underneath prefixes or merge them flatly:
let api = Router::new().route("/users", get(list_users));
let admin = Router::new().route("/dashboard", get(dashboard));
let app = Router::new()
.nest("/api/v1", api)
.merge(admin);| Return Type | Content-Type | Status |
|---|---|---|
&'static str / String |
text/plain |
200 |
Json<T: Serialize> |
application/json |
200 |
Html<T: Into<String>> |
text/html |
200 |
StatusCode |
— (empty body) | Any |
(StatusCode, T) |
Inherits from T |
Custom |
Result<T, E> |
Inherits from Ok/Err |
Auto |
Bytes / Vec<u8> |
application/octet-stream |
200 |
Handlers can return Result<T, Error> and use ? for error propagation. Errors produce secure JSON responses — internal details are never leaked.
| Layer | Description |
|---|---|
CorsLayer |
Full CORS spec, permissive() and very_permissive() presets |
CompressionLayer |
gzip, brotli, zstd, deflate response compression |
DecompressionLayer |
Request body decompression |
TimeoutLayer |
408 on slow handlers |
RequestIdLayer |
UUID v4 per request in x-request-id header |
TraceLayer |
Structured tracing spans (method, path, status, latency) |
SecurityHeadersLayer |
Full OWASP header suite (X-Frame-Options, HSTS, CSP...) |
SetResponseHeaderLayer |
Set/override/append response headers |
SensitiveHeadersLayer |
Redact sensitive headers in logs |
RateLimitLayer |
Token bucket per IP / header / global |
RequireAuthorizationLayer |
Bearer, Basic, or custom auth |
RequestBodyLimitLayer |
413 on oversized request bodies |
CatchPanicLayer |
500 on handler panics, no server crash |
MapRequestBodyLayer |
Transform request body bytes |
MapResponseBodyLayer |
Transform response body bytes |
CsrfLayer |
Double-submit cookie CSRF protection |
from_fn |
Middleware from a plain async function |
from_fn_with_state |
Same, with access to router state |
map_request |
Transform request only (no response) |
map_response |
Transform response only (no request) |
Multipart uploads are parsed as a stream. Large files can be processed chunk by chunk or written to a secure temporary file without buffering the full upload in memory.
use arvik::{Multipart, MultipartError};
use futures_util::StreamExt as _;
async fn upload(mut multipart: Multipart) -> Result<String, MultipartError> {
let mut files = 0;
while let Some(field) = multipart.next_field().await? {
if field.file_name().is_some() {
let temp = field.save_to_temp().await?;
files += 1;
println!(
"saved {:?}: {} bytes",
temp.metadata().file_name(),
temp.bytes_written()
);
} else {
let mut stream = field.into_progress_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
println!("field progress: {} bytes", chunk.bytes_read());
}
}
}
Ok(format!("received {files} files"))
}Configure limits and temp-file placement through request extensions from middleware:
use arvik::{middleware::map_request, MultipartConfig, Router, post};
let app = Router::new()
.route("/upload", post(upload))
.layer(map_request(|mut req: arvik::Request| async move {
req.extensions_mut().insert(
MultipartConfig::new()
.max_fields(20)
.max_field_size(10 * 1024 * 1024)
.max_total_size(100 * 1024 * 1024)
.with_temp_dir("/var/tmp/arvik-uploads"),
);
req
}));Full WebSocket support via arvik-ws, built on tokio-tungstenite. Auto-pong keeps connections alive with zero application boilerplate.
WebSocket is opt-in. Enable it by adding the
wsfeature to yourCargo.toml:# Opt in to WebSocket arvik = { version = "0.5", features = ["ws"] }Default build (
arvik = "0.5") is HTTP-only — no WebSocket compiled in.
use arvik::{Router, get};
use arvik::ws::{WebSocket, WebSocketUpgrade, Message};
async fn ws_handler(ws: WebSocketUpgrade) -> impl arvik::IntoResponse {
ws.on_upgrade(|mut socket| async move {
// Ping/Pong handled automatically — no extra match arm needed
while let Some(Ok(msg)) = socket.recv().await {
match msg {
Message::Text(text) => {
socket.send(Message::Text(format!("echo: {text}"))).await.ok();
}
Message::Binary(data) => {
socket.send(Message::Binary(data)).await.ok();
}
Message::Close(_) => break,
_ => {}
}
}
})
}
// With config + subprotocol negotiation
async fn chat_handler(ws: WebSocketUpgrade) -> impl arvik::IntoResponse {
ws.protocols(["chat", "json"])
.max_message_size(64 * 1024) // 64 KB
.max_frame_size(16 * 1024) // 16 KB
.on_upgrade(|socket| async move {
let (mut sender, mut receiver) = socket.split();
while let Some(Ok(msg)) = receiver.next().await {
sender.send(msg).await.ok();
}
})
}
let app = Router::new()
.route("/ws", get(ws_handler))
.route("/chat", get(chat_handler));Features at a glance:
| Feature | Detail |
|---|---|
| Auto ping/pong | recv() replies to Ping transparently |
| Split send/receive | socket.split() → (Sender, Receiver) |
Stream impl |
Receiver works with futures_util combinators |
| Subprotocol negotiation | .protocols(["chat", "json"]) |
| Configurable limits | max_message_size, max_frame_size |
| Typed rejections | WebSocketUpgradeRejection with correct HTTP codes |
| RFC 6455 compliant | SHA-1 accept key, full close code enum |
Full Server-Sent Events support via arvik-sse. Send real-time updates to the browser with built-in keep-alive pings and zero-allocation string rendering.
SSE is opt-in. Enable it by adding the
ssefeature to yourCargo.toml:arvik = { version = "0.5", features = ["sse"] }
use arvik::{Router, get};
use arvik::sse::{Event, KeepAlive, Sse};
use std::time::Duration;
use tokio_stream::StreamExt as _;
async fn json_stream() -> Sse<impl futures_util::Stream<Item = Result<Event, serde_json::Error>>> {
let stream = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(Duration::from_millis(500)),
)
.enumerate()
.map(|(i, _): (usize, _)| {
Event::default()
.event("metric")
.id(i.to_string())
.json_data(&serde_json::json!({ "seq": i }))
});
// Automatically send a `: \n\n` comment every 10 seconds to keep proxies alive
Sse::new(stream).keep_alive(KeepAlive::new().interval(Duration::from_secs(10)))
}
let app = Router::new().route("/stream", get(json_stream));arvik/
├── arvik/ # Facade crate (re-exports everything)
├── arvik-core/ # Core: Request, Response, Body, Handler, IntoResponse, Error
├── arvik-router/ # MethodRouter — HTTP method dispatch
├── arvik-hyper/ # Hyper 1.x server integration
├── arvik-extract/ # Extractors: Path, Query, Json, Form, Multipart
├── arvik-middleware/ # CORS, compression, timeout, auth, rate limits
├── arvik-ws/ # WebSocket support (v0.5.0 ✅)
├── arvik-sse/ # Server-Sent Events (v0.5.1 ✅)
├── arvik-static/ # Static file serving (coming in v0.6.x)
├── arvik-tls/ # TLS via rustls (coming in v0.6.x)
├── arvik-macros/ # Proc macros: #[handler], #[route] (coming in v0.7.x)
└── arvik-test/ # Testing utilities (coming in v0.7.x)
See ROADMAP.md for the complete version-by-version plan.
| Version | Focus | Status |
|---|---|---|
| 0.0.x | Foundation & Core | ✅ Complete |
| 0.1.x | Routing System | ✅ Complete |
| 0.2.x | Extractors | ✅ Complete |
| 0.3.x | Responses & Error Handling | ✅ Complete |
| 0.4.x | Middleware | ✅ Complete |
| 0.5.0 | WebSocket | ✅ Complete |
| 0.5.1 | Server-Sent Events (SSE) | ✅ Complete |
| 0.5.2 | Multipart Polish | ✅ Complete |
| 0.6.x | TLS, HTTP/2, Static Files | ⏳ Next |
| 0.7.x | Macros, Testing, Config | ⏳ Planned |
| 0.8.x | Observability & Security | ⏳ Planned |
| 0.9.x | Performance Sprint | ⏳ Planned |
| 0.10.x | Stabilization & Docs | ⏳ Planned |
See ARCHITECTURE.md for the full technical specification including all planned APIs, crate responsibilities, extractor system, middleware design, and performance architecture.
Arvik aims to unify extreme ergonomics with world-class performance. Here is how Arvik compares against the Rust heavyweights in a simple TCP path routing test (wrk -t4 -c100 -d10s), built in --release mode and run simultaneously on the same hardware.
| Framework | Version | Requests / sec | Latency (avg) | Underlying Engine |
|---|---|---|---|---|
| Actix-Web | v4 | 331,131 req/s |
483 µs |
Custom HTTP worker model |
| Axum | v0.8.x | 301,439 req/s |
349 µs |
Tokio / Hyper 1.x |
| Arvik | v0.3.4 | 307,177 req/s |
333 µs |
Tokio / Hyper 1.x |
Tested using wrk with 100 concurrent workers across 4 threads for 10 seconds. Arvik achieves performance completely matched with Axum out of the box, powered by its zero-allocation radix trie path routing.
Arvik is being built in public from 0.0.5. Contributions are welcome at every stage.
See CONTRIBUTING.md for the full guide — setup, coding standards, commit format, and PR process.
Quick start for contributors:
git clone https://github.com/AarambhDevHub/arvik.git
cd arvik
cargo check --workspace
cargo clippy --workspace -- -D warnings
cargo test --workspace| Platform | Link | Purpose |
|---|---|---|
| 💬 Discord | Aarambh Dev Hub | Questions, discussion, dev updates |
| 📺 YouTube | Aarambh Dev Hub | Build-in-public video series |
| 🐙 GitHub Discussions | Discussions | Feature proposals, Q&A |
| 🐛 GitHub Issues | Issues | Bug reports |
If Arvik has been useful to you, consider supporting the project:
Found a vulnerability? Please do not open a public issue. See SECURITY.md for responsible disclosure instructions.
Dual-licensed under MIT or Apache-2.0 at your option.
Copyright 2026 Aarambh Dev Hub
Arvik — Async Rust Velocity Integration Kit. Built by Aarambh Dev Hub. ⚡