Churust gives you Ktor's developer experience in Rust: an application engine, a
routing DSL, an install(plugin) system, and a phased interceptor pipeline —
built on a battle-tested async stack (tokio + hyper + rustls). Churust owns
the ergonomic layer; it does not reinvent HTTP parsing or TLS.
use *;
async
Install
[]
= "0.2"
That is the whole dependency list. Churust re-exports the runtime it is built
on as churust::tokio, and #[churust::main] uses that re-export, so no
separate tokio entry is needed. Add one only if you want a tokio feature
Churust does not enable — Cargo unifies the two.
Plugins and transports are opt-in features on the umbrella crate — depend on
churust and enable what you need rather than pulling in the plugin crates
directly:
= { = "0.2", = ["full", "ws", "fs", "tls"] }
| Feature | Pulls in | Gives you |
|---|---|---|
json |
churust-json |
Json<T> extractor/responder, content negotiation |
logging |
churust-logging |
CallLogging via tracing |
cors |
churust-cors |
preflight + CORS headers |
auth |
churust-auth |
Bearer/Basic/JWT, Principal<P> |
full |
all four above | the whole plugin set |
ws |
churust-core/ws |
WebSocket upgrade + WebSocket/Message |
fs |
churust-core/fs |
StaticFiles directory handler |
tls |
churust-core/tls |
rustls-backed HTTPS |
Default features are empty, so a plain churust = "0.2" compiles the core
engine and nothing else.
Every Churust crate is released in lockstep on one version number, so
churust-core, churust-json, and the rest always match the umbrella version.
Features
- Hybrid handlers — write call-style (
|call: Call|) or with typed extractors (Path<T>,Query<T>,State<T>,Json<T>,BearerToken,Principal<P>) — mix freely in one handler. install(plugin)— Ktor-style plugins composed over a named pipeline (Setup → Monitoring → Plugins → Call → Fallback) for deterministic order.- Built-in plugins — JSON content negotiation, request logging, CORS, and authentication (Bearer / Basic / JWT).
- Typed app state / DI —
.state(T)then extractState<T>. - Layered config — defaults <
churust.toml< env (CHURUST_*) < code DSL. - Secure by default — body-size limits, request timeouts, panic isolation (a panicking handler returns 500, never crashes the server), no version banner, opt-in rustls TLS.
- Fast, in-process tests —
TestClientdrives the full pipeline without binding a socket.
Workspace layout
| Crate | Docs | What it is |
|---|---|---|
churust |
docs.rs | Umbrella crate + prelude. Depend on this. Plugins behind features. |
churust-core |
docs.rs | Engine, routing, pipeline, Call, extractors, config, state, TLS, WebSockets, static files, test harness. |
churust-macros |
docs.rs | #[churust::main]. |
churust-json |
docs.rs | Json<T> extractor/responder + ContentNegotiation plugin (feature json). |
churust-logging |
docs.rs | CallLogging plugin via tracing (feature logging). |
churust-cors |
docs.rs | Cors plugin — preflight + headers (feature cors). |
churust-auth |
docs.rs | Auth (Bearer/Basic/JWT) + Principal<P> (feature auth). |
Runnable examples:
| Example | Run it | Shows |
|---|---|---|
examples/hello |
cargo run -p hello |
Minimal server, path params. |
examples/api |
cargo run -p api |
JSON CRUD with all four plugins + auth-gated routes. |
examples/chat |
cargo run -p chat |
WebSocket echo endpoint and a broadcast room. |
examples/static |
cargo run -p static-example |
StaticFiles plus a streamed response body. |
A fuller example
use *;
use ;
use Mutex;
async
$ curl localhost:8080/notes
[]
$ curl -X POST localhost:8080/notes -d '{"text":"hi"}'
{"error":"authentication required","status":401}
$ curl -X POST localhost:8080/notes -H 'authorization: Bearer admin-token' \
-H 'content-type: application/json' -d '{"text":"hi"}'
{"id":1,"text":"hi"}
WebSockets (feature ws)
Opt in with features = ["ws"]. A handler takes the WebSocketUpgrade
extractor and calls on_upgrade to switch the request into a bidirectional
socket (a plain GET to the route is rejected with 426 Upgrade Required):
use *;
use ;
r.get;
= { = "0.2", = ["ws"] }
See examples/chat for an echo endpoint plus a broadcast room.
Static files & streaming (feature fs)
Response bodies are a Body — either buffered bytes or a lazy stream — so large
or dynamic payloads never have to be fully materialized in memory. Body is
always available; StaticFiles is behind the opt-in fs feature.
use *; // brings StaticFiles into scope under `fs`
use Body;
r.get;
r.get;
StaticFiles detects the Content-Type from the file extension, serves an
optional index file for directories, rejects path traversal (.., absolute
paths, symlink escapes) with 404, and streams the file in chunks. See
examples/static.
= { = "0.2", = ["fs"] }
Core concepts
Handlers & extractors
A handler is an async closure/fn returning anything that implements
IntoResponse. Arguments are extractors:
FromCallParts(borrow the request) — any position:Path<T>,Query<T>,State<T>,BearerToken,Principal<P>.FromCall(consume the body) — last argument only:Json<T>,Call.
Call itself is the call-style base case (|call: Call| ...).
Pipeline & plugins
A Plugin registers Middleware into a Phase. Middleware own the Call,
may mutate it, call next.run(call), and post-process the Response (the onion
model). Phase order is fixed and deterministic regardless of install order.
Configuration
# churust.toml
[]
= "0.0.0.0"
= 8080
= 1048576
= 30000
[] # requires the `tls` feature
= "cert.pem"
= "key.pem"
Churust::from_config() loads churust.toml + CHURUST_* env vars; chained
DSL setters override. Example: CHURUST_SERVER_PORT=9090.
Testing
use TestClient;
let app = build_app;
let client = new;
let res = client.get.send.await;
assert_eq!;
Development
Requires Rust 1.96+ (the MSRV, and what CI pins).
Warnings are errors in CI. Before opening a PR, run the full gate — the exact
command list is in CONTRIBUTING.md, and
it covers fmt, the clippy feature matrix, the test feature matrix, the
examples, and a docs build.
Status
Published on crates.io — the badge above carries the current version. Pre-1.0, so the API is settling rather than settled: expect breaking changes in minor releases until 1.0.
Everything documented above works today:
- Core — routing, hybrid extractors, the four plugins, named phases, layered
config, typed state, timeouts, TLS,
#[churust::main] - WebSockets — opt-in
wsfeature - Streaming bodies — the always-on
Bodytype - Static files —
StaticFiles, opt-infsfeature
Deferred on purpose (YAGNI): sessions, response compression, HTTP/3, route-scoped middleware sugar. Want one of them? Make the case in Discussions.
Design specs live in
docs/design/
and implementation plans in
docs/plans/.
Those documents label work as v1 / v2.0 / v2.1 — those are internal build
milestones, not published versions.
Contributing
Contributions are welcome — read CONTRIBUTING.md first. The short version: Churust keeps a narrow scope, tests come first, and anything optional is feature-gated so default builds never change.
| Ask a question | Discussions → Q&A |
| Propose a feature | Discussions → Ideas |
| Report a bug | Issues |
| Report a vulnerability | SECURITY.md — privately, never a public issue |
| Get help | SUPPORT.md |
| Cut a release (maintainers) | RELEASING.md |
Everyone taking part is held to the Code of Conduct.
Sponsor
Churust is built and maintained in spare time. If it saves you some, you can support the work through GitHub Sponsors.
Sponsorship funds maintenance — issue triage, security response, keeping up with tokio/hyper/rustls releases — not feature bounties. Features are decided on merit in Discussions, and that stays true regardless of who is sponsoring.
License
MIT — see LICENSE.