One handler set. Every version of HTTP.
Livery is a BEAM-native web framework that serves the same router and middleware over HTTP/1.1, HTTP/2, and HTTP/3 from a single runtime. WebSocket, WebTransport, Server-Sent Events, OpenAPI, MCP, and OpenTelemetry-style observability are built-in modules. It is written in the spirit of Axum + Tower + Hyper, on Erlang/OTP.
%% rebar.config
{deps, [{livery, {git, "https://github.com/benoitc/livery.git", {branch, "main"}}}]}.A handler takes a request value and returns a response value. Compile a router, start a service, and you are serving:
Router = livery_router:compile([
{<<"GET">>, <<"/">>, fun(_Req) -> livery_resp:text(200, <<"hello">>) end}
]),
{ok, _Pid} = livery:start_service(#{http => #{port => 8080}, router => Router}).$ curl localhost:8080/
helloRun it now: rebar3 shell, paste the two expressions, then curl.
show_user(Req) ->
Id = livery_req:binding(<<"id">>, Req),
livery_resp:json(200, json:encode(#{id => Id, name => <<"Ada">>})).
Router = livery_router:compile([
{<<"GET">>, <<"/users/:id">>, fun show_user/1},
{<<"POST">>, <<"/users">>, {users, create}} %% {Module, Function}
]).Middleware is a continuation over immutable values: call(Req, Next, State). Attach it service-wide or per route.
livery:start_service(#{
http => #{port => 8080},
router => Router,
middleware => [
{livery_request_id, undefined},
{livery_access_log, #{}},
{livery_body_limit, #{max => 1048576}}
]
}).The same router and middleware over H1, H2, and H3, advertising H3 via
Alt-Svc:
livery:start_service(#{
http => #{port => 80},
https => #{port => 443, cert => Cert, key => Key},
http3 => #{port => 443, cert => Cert, key => Key},
router => Router,
alt_svc => advertise
}).Need just one protocol? livery:start_listener(livery_h1, #{port => 8080, router => Router}).
The client mirrors the middleware model outbound: stack timeouts, retries, a circuit breaker, or load balancing around a request.
Client = livery_client:new(#{
base_url => <<"https://api.example.com">>,
stack => [livery_client:timeout(5000), livery_client:retry(#{max => 3})]
}),
{ok, Resp} = livery_client:get(Client, <<"/health">>),
200 = livery_client:status(Resp).events(_Req) ->
livery_resp:sse(200, fun(Emit) ->
Emit(#{event => <<"tick">>, data => <<"1">>}),
Emit(#{event => <<"tick">>, data => <<"2">>})
end).Chunked bodies, NDJSON, file responses with byte ranges, WebSocket and WebTransport over H2/H3 work the same way.
- One handler, three wires — write a handler once; serve it over H1, H2, and H3 with shared routing, middleware, and Alt-Svc upgrade.
- Tower-style middleware — value-based
call(Req, Next, State)pipelines, composable per service or per route, in both directions (server-inbound and the outboundlivery_client). - Streaming — chunked, SSE, NDJSON, WebSocket and WebTransport over H2/H3, and file responses with byte ranges.
- OpenAPI — generate a 3.1 document from routes, serve Redoc or Swagger UI, and validate request bodies against a JSON-Schema subset.
- MCP — serve the Model Context Protocol Streamable HTTP transport on the main listener.
- Observability & auth — OpenTelemetry-style traces/metrics, trace-correlated logs, JWT/JWKS/OIDC, signed sessions, introspection.
Full guides, tutorials, and the generated API reference live at
https://benoitc.github.io/livery/. The same content is in the repo
under docs/: start with the
Quickstart, then the
tutorials and
how-to guides. For contributors, see
AGENTS.md.
Livery is developed and maintained by Enki Multimedia. If your company relies on it, reach out for sponsored support, or sponsor its maintenance via GitHub Sponsors.
Apache-2.0.