Simple by design
Express-like routing and middleware. Handlers return plain values: text, Json(..), (201, body), Option or Result.
Axeon 1.0 is here
Write handlers as simply as in Express and get Rust's speed, memory efficiency and safety. Built on hyper, batteries included, and ready for production.
$ cargo add axeon
use axeon::Server;
fn main() {
let mut app = Server::new();
app.get("/", |_req| async { "Hello, World!" });
app.listen("127.0.0.1:3000")
.expect("server failed to start");
}
$ cargo run
$ curl -i http://127.0.0.1:3000/
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
Hello, World!
Apple M1, 128 keep-alive connections, best of three runs. Methodology and caveats.
Small surface, no surprises. The pieces you reach for in every project are already there and already secure.
Express-like routing and middleware. Handlers return plain values: text, Json(..), (201, body), Option or Result.
Within ~10% of the fastest Rust frameworks, at about 4–5× Express throughput on 1/100th of the memory. Routing time doesn't grow with route count.
Validation, JWT, cookies, sessions, CSRF, typed config, templates, static files, uploads, server-sent events and WebSockets.
Size limits, timeouts, slow-client protection, security headers, rate limiting, CORS, panic isolation and constant-time secret comparison.
HTTP/1.1, HTTP/2 and HTTPS, graceful shutdown with a drain period, health checks, Prometheus metrics and request IDs.
Use any tower layer (all of tower-http) as middleware, or run an Axeon app as a tower Service. No database lock-in.
Typed helpers parse input and answer bad input with the right status code. Middleware wraps what you choose. Everything is plain Rust, with no macros to learn.
app.get("/users/:id", |req| async move {
let id: u64 = req.param("id")?; // 400 if not a number
Ok::<_, ServerError>(Json(json!({ "id": id })))
});
app.post("/users", |req| async move {
let user: NewUser = req.json()?; // 415 / 400 / 422 for bad input
Ok::<_, ServerError>((201, Json(user)))
});app.middleware(from_fn(|req, next| async move {
let start = Instant::now();
let res = next.handle(req).await?;
println!("{} in {:?}", res.status, start.elapsed());
Ok(res)
}));
app.layer(TimeoutLayer::with_status_code( // any tower layer works too
StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(10),
));let jwt = Jwt::hs256(&secret);
let mut api = Router::new();
api.middleware(jwt.middleware::<Claims>()); // 401 without a valid token
api.get("/me", |req| async move {
req.extension::<Claims>().map(|c| c.sub.clone())
});
app.mount("/api", api);app.get("/ws", |mut req| async move {
req.upgrade_websocket(|mut socket| async move {
while let Some(Ok(message)) = socket.recv().await {
if socket.send(message).await.is_err() {
break;
}
}
})
});#[tokio::test]
async fn says_hello() {
let app = app().into_app().await.unwrap();
let res = app.call(Request::new(Method::GET, "/hello/Ada")).await;
assert_eq!(res.status, 200);
assert_eq!(res.body_text(), "Hello, Ada!");
}24 complete, copy-pasteable recipes. Every program is compiled and tested against Axeon in CI, so what you copy works.
A server in a handful of lines. Create a project, add Axeon, and answer your first request.
Open recipe Getting startedPath parameters, wildcards, route groups, and how conflicts are settled.
Open recipe Requests and responsesParse JSON bodies, query strings and typed path parameters, and get the right 4xx status for free.
Open recipe Getting startedCRUD for one resource with shared state, validation, proper status codes and tests, in one file.
Open recipe Requests and responsesCheck incoming data and answer 422 with a per-field list of problems.
Open recipe Requests and responsesReturn typed errors with ?, shape every error response yourself, and keep internals out of the response.
Open recipe Requests and responsesStatus codes, headers, redirects, downloads and streaming, when a plain return value is not enough.
Open recipe Middleware and stateClosure middleware, middleware as a type, scoping to a router or one route, and passing data to handlers.
Open recipe Middleware and stateRegister database pools, clients and counters once and read them from any handler.
Open recipe Middleware and stateSecurity headers, CORS, rate limiting, compression and request limits in one setup.
Open recipe Auth and sessionsIssue tokens at login and protect a router so handlers only run for valid bearer tokens.
Open recipe Auth and sessionsCheck opaque tokens with your own async lookup, and protect internal tools with HTTP Basic.
Open recipe Auth and sessionsA browser login flow with server-side sessions, signed cookies and CSRF protection.
Open recipe Data and storageLoad typed settings from environment variables and a .env file, with every problem reported at once.
Open recipe Data and storageModels, typed queries, migrations and pagination on SQLite or PostgreSQL, wired into Axeon errors.
Open recipe Files and pagesServe a directory with range requests and caching, and fall back to index.html for client-side routes.
Open recipe Files and pagesServer-rendered pages with auto-escaping templates, layouts and form handling.
Open recipe Files and pagesAccept multipart uploads and stream them straight to disk, with a per-route size limit.
Open recipe RealtimePush live updates to the browser over a plain HTTP response.
Open recipe RealtimeEcho server and a broadcast chat room, using Tokio's broadcast channel.
Open recipe ProductionLogging with request IDs, metrics, health checks, limits, timeouts and graceful shutdown.
Open recipe ProductionCall routes in-process, with no sockets and no ports, and assert on status, headers and body.
Open recipe ProductionPlug in any tower layer, including all of tower-http, and run Axeon as a tower Service.
Open recipe ProductionServe TLS directly with rustls and get HTTP/2 negotiated automatically.
Open recipeNo recipes match. Try another word, or ask for one.
Within spitting distance of the fastest Rust frameworks, and an order of magnitude ahead of Node.
| Framework | JSON | Path param | p99 latency | Memory |
|---|---|---|---|---|
| actix-web 4 | 207k req/s | 190k req/s | 5–9 ms | 10.6 MiB |
| axum 0.8 | 195k req/s | 197k req/s | 2–3 ms | 8.5 MiB |
| Axeon | 176k req/s | 185k req/s | 2 ms | 8.9 MiB |
| Express 5 (8 workers) | 39k req/s | 45k req/s | 14–22 ms | 877 MiB |
Apple M1, 128 keep-alive connections, best of three runs. Read the full methodology before drawing conclusions for your workload.
cargo new hello && cd hellocargo add axeoncargo run