# Quick Start

> Four paths to a running rust-web-server in under five minutes — static files, a first route, a config-driven proxy, and automatic HTTPS.

Source: https://rws8.tech/docs/getting-started/quick-start/ · Last updated 2026-11-06

To run rws, install it (`brew install bohdaq/tap/rws`, `cargo binstall rust-web-server` or `docker run -p 7878:7878 -v "$PWD:/srv" bohdaq/rws`) and start `rws` in a folder: it serves that folder on http://localhost:7878. The four paths below add a route, a proxy and automatic HTTPS; each ends 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.

```bash
brew install bohdaq/tap/rws      # or: cargo binstall rust-web-server
# create something to serve
mkdir www && echo "<h1>Hello</h1>" > www/index.html
cd www
rws
```

```
Server is up and running at: http://0.0.0.0:7878
```

```bash
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. A directory with no `index.html` renders a directory listing page instead of `404` — try `mkdir www/uploads && touch www/uploads/report.pdf && curl http://localhost:7878/uploads/`.

:::note[HTTPS in one flag]
If you have a TLS certificate, pass `--tls-cert-file=cert.pem --tls-key-file=key.pem` and the server automatically upgrades to HTTP/2 + HTTP/3:

```bash
rws --tls-cert-file=cert.pem --tls-key-file=key.pem
```
:::

---

## Path 2 — First route (library)

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

### 1. Add dependencies

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

### 2. Write a handler

```rust title="src/main.rs"
use rust_web_server::prelude::*;

fn hello(_req: &Request, _params: &PathParams, _conn: &ConnectionInfo, _state: &()) -> Response {
    Response::text(STATUS_CODE_REASON_PHRASE.n200_ok, "Hello, world!")
}

#[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

```bash
cargo run
```

```bash
curl http://localhost:7878/hello
# Hello, world!
```

### Handler signature

Every route handler receives four arguments:

| Argument | Type | What it contains |
|---|---|---|
| `_req` | `&Request` | Method, URI, headers, raw body bytes |
| `_params` | `&PathParams` | Named path segments (e.g. `:id`) |
| `_conn` | `&ConnectionInfo` | Client/server IP, port, SNI hostname |
| `_state` | `&S` | Your shared application state |

### Shared state

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

```rust
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::text(STATUS_CODE_REASON_PHRASE.n200_ok, &format!("count: {n}"))
}

#[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

```toml title="rws.config.toml"
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"
```

```bash
rws
# Server is up and running at: http://0.0.0.0:8080
```

```bash
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. A `path` ending in `*` is a prefix match (`/api/*` matches `/api/users`); without it the path must match exactly. Requests no route matches fall through to the static-file server.
- `type = "proxy"` forwards the request to the named upstream. `type = "respond"` returns a fixed response.

:::note[Add middleware per route]
Rate limiting, Bearer auth, and request rewriting can all be added per-route inside the config file — no code change needed. See the Proxy & Gateway documentation for the full schema.
:::

:::tip[HTTPS in front of the proxy]
Add an `[acme]` section (Path 4) and `rws` gets Let's Encrypt certificates for the proxy itself. To use your own certificates instead, set `tls_cert_file` / `tls_key_file`; per-domain certificates go in `[[virtual_host]]` blocks.
:::

---

## Path 4 — Automatic HTTPS (no code)

On a server whose DNS points at it, with ports 80 and 443 reachable:

```toml title="rws.config.toml"
port               = 443
http_redirect_port = 80

[acme]
domains = ["example.com"]
email   = "you@example.com"
```

```bash
sudo rws     # or grant the binary CAP_NET_BIND_SERVICE to bind 80/443 without root
```

On first start `rws` requests a certificate from Let's Encrypt (stored as
`cert.pem` / `key.pem`), then serves HTTPS with HTTP/2 and HTTP/3 and
redirects HTTP to HTTPS. Renewal runs in the background. Try it against the
Let's Encrypt staging CA first with `staging = true` — see
[Automatic HTTPS](/features/acme/).

```bash
curl -I http://example.com/      # 301 → https://example.com/
curl -I https://example.com/     # 200, valid certificate
```

---

## Next steps

- **[Dev Workflow](/getting-started/dev-workflow/)** — auto-rebuild and restart on every code change with `cargo-watch`
- **Routing** — named path parameters (`:id`), wildcards, virtual-host routing
- **Middleware** — `RateLimitLayer`, `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`
