Skip to content

Quick Start

Pick the path that matches your goal. All three end with a working server you can hit with curl.


Path 1 — Static file server (zero code)

Install the binary and point it at a directory. No config file, no code.

Terminal window
cargo install rust-web-server
# create something to serve
mkdir www && echo "<h1>Hello</h1>" > www/index.html
cd www
rws
Listening on 0.0.0.0:7878
Terminal window
curl http://localhost:7878/
# <h1>Hello</h1>

rws serves every file under the current directory, handles range requests, sets ETags, negotiates gzip, and returns 404 for missing files — all with no configuration.


Path 2 — First route (library)

Use rust-web-server as a Rust library crate and define your own handlers.

1. Add dependencies

Cargo.toml
[dependencies]
rust-web-server = "17"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

2. Write a handler

src/main.rs
use rust_web_server::prelude::*;
fn hello(_req: &Request, _params: &PathParams, _conn: &ConnectionInfo, _state: &()) -> Response {
Response::get_response(
STATUS_CODE_REASON_PHRASE.n200_ok,
None,
Some(vec![Range::get_content_range(
b"Hello, world!".to_vec(),
MimeType::TEXT_PLAIN.to_string(),
)]),
)
}
#[tokio::main]
async fn main() {
let app = routes! {
App::with_state(()),
GET "/hello" => hello,
};
let (listener, pool) = Server::setup().unwrap();
tokio::join!(
Server::run_tls(listener, pool, app.clone()),
Server::run_quic(app),
Server::run_redirect(),
);
}

3. Run and verify

Terminal window
cargo run
Terminal window
curl http://localhost:7878/hello
# Hello, world!

Handler signature

Every route handler receives four arguments:

ArgumentTypeWhat it contains
_req&RequestMethod, URI, headers, raw body bytes
_params&PathParamsNamed path segments (e.g. :id)
_conn&ConnectionInfoClient/server IP, port, SNI hostname
_state&SYour shared application state

Shared state

Replace () with any Send + Sync type to share state across handlers:

use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
struct AppState {
counter: AtomicU64,
}
fn count(_: &Request, _: &PathParams, _: &ConnectionInfo, state: &Arc<AppState>) -> Response {
let n = state.counter.fetch_add(1, Ordering::Relaxed);
Response::get_response(
STATUS_CODE_REASON_PHRASE.n200_ok,
None,
Some(vec![Range::get_content_range(
format!("count: {n}").into_bytes(),
MimeType::TEXT_PLAIN.to_string(),
)]),
)
}
#[tokio::main]
async fn main() {
let state = Arc::new(AppState { counter: AtomicU64::new(0) });
let app = routes! {
App::with_state(state),
GET "/count" => count,
};
let (listener, pool) = Server::setup().unwrap();
tokio::join!(
Server::run_tls(listener, pool, app.clone()),
Server::run_quic(app),
Server::run_redirect(),
);
}

Path 3 — Config-driven proxy (no code)

Drop an rws.config.toml next to the rws binary and it becomes a reverse proxy and load balancer. No code required.

Minimal config

rws.config.toml
[server]
port = 8080
[[upstream]]
name = "api"
backends = ["localhost:3000", "localhost:3001"]
[upstream.health_check]
path = "/healthz"
interval_secs = 10
healthy_threshold = 2
[[route]]
[route.match]
path = "/api/"
[route.action]
type = "proxy"
upstream = "api"
[[route]]
[route.match]
path = "/"
[route.action]
type = "respond"
status = 200
body = "Gateway ready"
Terminal window
rws
# Listening on 0.0.0.0:8080
Terminal window
curl http://localhost:8080/
# Gateway ready
curl http://localhost:8080/api/users
# proxied to localhost:3000 or localhost:3001 (round-robin, health-checked)

What the config does

  • [[upstream]] declares a named backend pool with health checking. Dead backends are removed automatically; they re-enter rotation once they pass healthy_threshold consecutive checks.
  • [[route]] rules are evaluated top-to-bottom, first match wins.
  • type = "proxy" forwards the request to the named upstream. type = "respond" returns a fixed response.

Next steps

  • Routing — named path parameters (:id), wildcards, virtual-host routing
  • MiddlewareRateLimitLayer, JwtLayer, CacheLayer, OtelLayer, and more via .wrap(layer)
  • MCP Server — expose tools and resources to AI agents over the MCP Streamable HTTP protocol
  • ORM#[derive(Model)], QueryBuilder, migrations, HasMany / HasOne / BelongsTo