Expand description
§Actus
The pragmatic web framework for Rust: auditable controllers, persistent services, real HTTP — out of the box. Built directly on hyper and tokio; there is no separate server to run it on.
Actus gives you a clear two-tier structure — a top-level routing blueprint and self-contained controllers — while letting you mix REST, RPC-style actions, and legacy URL migrations in the same codebase. A reviewer can answer what endpoints exist, what they require, and who can call them by reading two macros, without grepping for attribute decorators across files.
§Philosophy
Most Rust web frameworks are either unopinionated (you invent the structure) or rigidly opinionated (you bend to their paradigm). Actus picks a middle:
- A clear hierarchy. The whole URL layout is declared once, in
app_routes! { ... }— the entire backend is visible at a glance. - A clear unit of code. Each controller owns a URL prefix and declares
its routes, access points, and parameters in one
routes! { ... }block. - Pragmatism inside that structure. REST verbs (
GET/POST/PUT/DELETE), RPC-style action names (/charge,/refund), path parameters ({id}), and legacy URLs (login.php) all coexist in the same block.
§Design principles
- Two kinds of cross-cutting concern get two shapes. HTTP-protocol
concerns (CORS, body limits, compression) are named
Server::with_X(...)methods with their lifecycle position built in; application concerns (logging, auth gates, request IDs, rate-limit policy) areMiddleware. You never have to position CORS in a stack. - Auditability over uniformity. “What does this server do?” and “what
endpoints exist?” are answerable from
Server::new(...)and the two macros — without walking a chain of layers. - Explicit over magic. No DI container, no extractors reaching into thin
air: the
app_routes!depsblock is constructor injection, and routes are declared, not discovered. - HTTP correctness out of the box. You shouldn’t need to know that compression goes outermost, or that the body cap gates the body parse — that is framework knowledge, not application knowledge.
- Policy-agnostic. No roles, no
Accessenum, no built-in RBAC. Authorization lives in your policy layer, called from a controller’spreparehook or a handler.
This crate is the façade you depend on. It re-exports the public API of the
implementation crates (actus-server, actus-controller,
actus-reply) and the two macros that declare your application’s URL
surface. Add it with:
[dependencies]
actus = "1.0"
tokio = { version = "1", features = ["full"] }
serde_json = "1"Optional features: compression (gzip/brotli responses), websocket
(ws::upgrade), and openapi (OpenAPI 3.x generation).
§Quick start
Two macros declare everything: routes! (one controller’s API surface) and
app_routes! (the whole application’s URL blueprint). A reviewer can see
every endpoint by reading just those two places.
use actus::prelude::*;
use serde_json::json;
// A controller owns a URL prefix and declares its routes in one block.
struct Greeter;
#[controller]
impl Greeter {
routes! {
GET "" => index(),
GET "{name}" => greet(name: String),
}
pub async fn index(&self) -> Reply {
reply!(json!({ "hello": "world" }))
}
pub async fn greet(&self, name: String) -> Reply {
reply!(json!({ "hello": name }))
}
}
// The application's URL blueprint, declared in one place. (The `deps`
// block — for injected services — is optional and omitted here.)
app_routes! {
routes {
"greet" => Greeter,
}
}
// `init()` is generated by `app_routes!`; it builds the router.
#[tokio::main]
async fn main() -> actus::InitResult<()> {
let router = init().await?;
Server::new(router).run(3000).await?;
Ok(())
}§What’s in the box
- Hyper-based HTTP server —
Server::run(port)binds127.0.0.1;Server::run_on(addr)binds anywhere (e.g.0.0.0.0:port). Graceful shutdown on SIGTERM / SIGINT with a configurable drain deadline. - Two-macro routing —
app_routes!(the app’s URL blueprint, with adepsblock for injected services) and#[controller]+routes!(per-controller verbs, path patterns, typed query/body extraction, apreparehook, and per-controllermax_body_bytes/rate_limit). - Longest-prefix routing at arbitrary depth, with a trailing
{...rest}catch-all and distinct404vs405(carryingAllow). - Typed extraction & state — query as a multimap, form-urlencoded
bodies, and typed path/query/body params;
preparehooks stash typed values viaparams.insert::<T>(...)that handlers read back. - Replies —
reply!for JSON, chunked streams, and Server-Sent Events;WebErrorfor structured RFC 7807application/problem+json. - HTTP-protocol features —
Server::with_cors,with_compression(gzip/brotli,compressionfeature), a per-request timeout, and three DoS guards (max connections, in-flight body budget, header-read timeout). - WebSocket (
websocketfeature) —ws::upgrade(...)from a handler. - OpenAPI 3.x (
openapifeature) —openapi::generate(...)walks the route tree and emits a spec. - Middleware —
before/afterhooks viaServer::with_middleware; ships aRequestLogger.
See the prelude for the common imports, and the repository for the
full guide — philosophy, framework comparisons, and the examples/
directory with auth, typed bodies, CORS, compression, WebSockets, SSE, and
middleware in working code.
Modules§
- openapi
- OpenAPI 3.x doc generation —
openapi::generate,openapi::Options. Available with theopenapifeature. OpenAPI 3.1 doc generation. Behind theopenapifeature. - prelude
- Common imports for Actus applications.
- ws
- WebSocket support —
ws::upgrade,ws::WebSocket,ws::Message. Available with thewebsocketfeature. WebSocket support (RFC 6455). Behind thewebsocketfeature.
Structs§
- Finalizer
- Converts a
ReplyDatainto a concretehyperHTTP response — setting status, headers, and body, and driving buffered, streaming, SSE, and connection-upgrade replies. - Rate
Limit Class - One controller’s declared rate-limit class, as returned by
Router::rate_limit_classes: the controller’smountpath and theclasslabel it declared via#[controller(rate_limit = "…")]. Used by a startup coverage check that asserts every declared class has a policy. - Router
- The Actus router. Dispatches requests to controllers by longest-prefix match over the route tree at arbitrary depth.
- Router
Builder - A builder for constructing the router.
- Server
- The main Actus server.
Constants§
- GIB
- One gibibyte (1024 × 1024 × 1024 bytes).
- KIB
- One kibibyte (1024 bytes). For readable byte-size limits, e.g.
#[controller(max_body_bytes = 4 * KIB)]. - MIB
- One mebibyte (1024 × 1024 bytes), e.g.
Server::with_max_body_bytes(2 * MIB).
Type Aliases§
- Init
Error - Error type used by
app_routes!’s generatedinit()for startup-time failures (DB connection refused, env var missing, migrations failing, etc.). Aliased toanyhow::Errorso any error implementingstd::error::Error + Send + Sync + 'staticconverts via?, and the generatedinit()slots cleanly into ananyhow::Result<()>main. - Init
Result Result<T, actus::InitError>. The macro-generatedinit()returns this.