You write a few routes. Viltrum owns the rest of the wire: accept loop, HTTP/1.1, keep-alive, hijack, WebSocket frames. No third-party stack under the hood. No ORM, sessions, or template engine pretending to be a framework.
Built for V. Cleartext first; put Caddy or nginx in front when you want edge TLS. Optional in-process HTTPS/WSS via app.listen_tls (mbedtls) for single-binary demos.
git clone https://github.com/Tuntii/viltrum.git && cd viltrum
bash scripts/install.sh # ~/.vmodules/viltrum → this tree
v run examples/hello
# curl http://127.0.0.1:8080/Needs v on your PATH.
module main
import viltrum {
new
recover
text
json
Request
Response
}
fn main() {
mut app := new()
app.use(recover)
app.get('/', fn (req Request) Response {
return text(200, 'ok\n')
})
app.get('/hi/:name', fn (req Request) Response {
name := req.param('name') or { 'world' }
return json(200, '{"hi":"${name}"}')
})
app.listen('127.0.0.1:8080') or { panic(err) }
}Selective imports keep handlers short. Full prefixes (import viltrum + viltrum.new()) work if you prefer them.
Same process, same Conn model. Text and binary, ping/pong, size limits. Over TLS: same app.ws + app.listen_tls → wss:// (see docs/ws.md, docs/tls.md).
import viltrum { new, WsSocket }
mut app := new()
app.ws('/ws', fn (mut s WsSocket) {
for {
msg := s.read_message() or { break }
if msg.is_text() {
s.write_text(msg.text()) or { break }
}
}
s.close_quiet()
})
app.listen('127.0.0.1:8084') or { panic(err) }v run examples/ws_echo
websocat ws://127.0.0.1:8084/ws
bash scripts/dev-cert.sh
v run examples/wss_echo
websocat -k wss://127.0.0.1:8444/wsimport viltrum { new, Conn, Request, switching_protocols }
app.upgrade('GET', '/echo', fn (mut c Conn, req Request) {
c.write_all(switching_protocols('echo').to_bytes()) or { return }
// you own the bytes after 101
c.close() or {}
})| HTTP/1.1 | Keep-alive, Host check, body size limits, optional Date/Server |
| Router | :param, trailing *wildcard, HEAD falls back to GET |
| App | mount, chain, cors, static files, logger, recover |
| Bodies | Content-Length only. Chunked / TE → 400 (no desync games) |
| Upgrade | One path: app.upgrade + Conn |
| WebSocket | app.ws on that path (ws://) |
Nothing here pretends to replace your reverse proxy.
v0.5, -prod, developer machine (Ryzen 7 class). Re-run yourself.
| Workload | Rough result |
|---|---|
GET / sustained (oha, 10s, c=50) |
~60–85k req/s |
| WebSocket echo (Python client) | ~11–23k msg/s (client-bound; lower bound) |
bash benches/run.sh
bash benches/run_ws.shMethod notes: benches/RESULTS.md.
| Read when | Doc |
|---|---|
| First hour | docs/getting-started.md |
| Request shape / middleware | docs/request-response.md |
| Accept → close | docs/connection.md |
| Hijack contract | docs/upgrade.md |
| WebSocket limits | docs/ws.md |
| Proxy deploy | docs/deploy.md |
| Full index | docs/README.md |
| Example | Port |
|---|---|
examples/hello |
8080 |
examples/rest |
8081 |
examples/features |
8082 |
examples/upgrade_echo |
8083 |
examples/ws_echo |
8084 |
v test http/ && v test router/ && v test engine/ && v test ws/Shipped (0.5): cleartext HTTP + first-party ws://.
Next product cut (0.6): in-process TLS, then wss:// on the same code. Track use-cases in #1.
0.5.x patches: harden timeouts, soak tests, measured WS/HTTP perf (epic #3).
Not on the menu: HTTP/2–3, auth/session platforms, “be Caddy.” Details: ROADMAP.md.
Setup and commit style: CONTRIBUTING.md.
Releases: conventional commits → semantic-release on main.
Pick something small, ship a tight PR, leave the API boring.
| Issue | Why it is a good first step |
|---|---|
| #8 UTF-8 on text frames | Clear RFC behavior (close 1007), opt-in flag, unit tests, short doc note |
| #5 Soak / close-storm harness | Script or test helper; assert echo correctness and server still alive |
| #7 First-party WS bench client | Replace Python as the headline loadgen; wire into benches/run_ws.sh |
All open GFI:
https://github.com/Tuntii/viltrum/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
Harder work (timeout policy, buffer reuse profiles) lives under the same epic; start there only if you already know the code.