Simple by design
Express-like routing and middleware. Handlers return plain values: text, Json(..), (201, body), Option or Result.
Axeon 1.2 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 and API keys, sessions, CSRF, rate limits, idempotency, caching, templates, 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.
Optional and versioned on their own. Axeon itself stays small and depends on no database.
axeon-ormModels, typed queries, tenant scopes, migrations, full-text and vector search.
See the recipeaxeon-jobsDurable background jobs on PostgreSQL: retries, cron schedules and priorities.
See the recipeaxeon-redisShared sessions, rate limits, idempotency keys and broadcast on Redis.
See the recipeaxeon-openapiA validated OpenAPI 3.1 document generated from your routes.
See the recipeaxeon-clientAn outbound HTTP client with SSRF protection, retries and circuit breaking.
See the recipeaxeon-otelOpenTelemetry: a span per request, trace context and OTLP export.
See the recipeaxeon-webhooksSigned webhooks with timestamps, replay limits and secret rotation.
See the recipeTyped 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!");
}44 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 Requests and responsesAnswer 304 Not Modified, set Cache-Control, and skip the expensive work when the client already has the data.
Open recipe Requests and responsesPick the best supported language per request from Accept-Language, a query parameter or a cookie.
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 Public APIsIssue keys that are stored only as hashes, authenticate them on every request, and limit what each key may do.
Open recipe Auth and sessionsA browser login flow with server-side sessions, signed cookies and CSRF protection.
Open recipe Auth and sessionsShare sessions and live updates across every instance with axeon-redis.
Open recipe Public APIsPer-client limits that differ by plan, daily quotas, standard RateLimit headers, and one limit shared by every instance.
Open recipe Public APIsMake POST requests safe to retry: a repeated Idempotency-Key gets the first response instead of running twice.
Open recipe Public APIsSend webhooks your customers can verify, and verify the ones you receive, with timestamps and secret rotation.
Open recipe Multi-tenant appsServe storefronts from subdomains and customers' own domains, and keep the admin app on its own host.
Open recipe Multi-tenant appsMake cross-tenant leaks impossible by default: queries without a tenant fail instead of returning everyone's rows.
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 Data and storageInsert or update in one statement, load thousands of rows quickly, and store enums as text.
Open recipe Data and storagePage through large tables at constant cost, and stop two editors from silently overwriting each other.
Open recipe Data and storageSearch text with PostgreSQL full-text, find similar items with pgvector, and combine both into one ranked list.
Open recipe Data and storageDurable jobs on PostgreSQL with retries, delays, unique keys and graceful shutdown.
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 Files and pagesCap each file, check its real type from the first bytes, and hash it while streaming to disk or any writer.
Open recipe RealtimePush live updates to the browser over a plain HTTP response.
Open recipe RealtimePublish a message once and stream it to every subscriber on every instance, with resume after a reconnect.
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 ProductionCap how long any route may take, override it per route, and answer 503 instead of hanging.
Open recipe ProductionCounters, gauges and histograms on the same Prometheus endpoint as the HTTP metrics.
Open recipe ProductionA span per request, trace context passed to other services, and export to any OTLP collector.
Open recipe ProductionAn outbound HTTP client with SSRF protection, timeouts, retries and a circuit breaker.
Open recipe ProductionDescribe your routes once and serve a validated OpenAPI 3.1 document for clients and docs.
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