Get started
~30 secmix archive.install hex francis
mix francis.new my_app
Deploy
~2 minmix francis.release
fly launch
The whole app
lib/my_app.exdefmodule MyApp do
use Francis
get "/", fn _ -> "Hello from Francis" end
get "/users/:id", fn conn -> %{id: conn.params["id"]} end
post "/echo", fn conn -> conn.body_params end
ws "/ws", fn {:received, msg}, _socket -> {:reply, msg} end
unmatched fn _ -> "not found" end
end
mix francis.new my_app writes this file and the project around it. iex -S mix francis.server runs it.
What you can build
6 primitivesHTTP Actions
get "/hello", fn _ -> %{hello: :world} end
get "/:name", fn %{params: params} -> params["name"] end
post "/", fn conn -> conn.body_params end
Maps become JSON, strings are sent as-is — use html/2 or text/2 to set the type. URL params, body, and the raw conn always available.
WebSockets
ws "/chat", fn
:join, socket -> {:reply, "welcome"}
{:received, msg}, _ -> {:reply, msg}
end
Pattern match on join, close, incoming frames, and messages from other processes. Heartbeat and frame size limits included.
Server-Sent Events
sse "/events", fn
:join, socket -> {:reply, %{event: "hello", data: socket.id}}
{:received, update}, _ -> {:reply, update}
end
# from any process holding socket.transport
send(transport, %{event: "price", data: 42})
Server to client only: push from any process via send/2. Named events, retry, and keepalive managed for you.
Static Files
use Francis, static: [ from: "priv/static", at: "/" ]
Serve from disk with gzip. Run mix francis.digest for content-hashed filenames and a cache manifest.
Security
plug Francis.Plug.SecureHeaders
plug Francis.Plug.CSP, directives: %{"script-src" => "'self'"}
safe_html conn, user_content # auto-escapes <>&"'
Secure headers and CSP with safe defaults — this page is served with both. Open redirects blocked in response helpers by design.
Plug Ecosystem
plug Plug.BasicAuth, username: "user", password: "pass"
plug Plug.Logger
plug MyApp.RateLimiter
Any standard Plug middleware works. No lock-in, no adapters needed.
Honest about scope
Francis is a thin DSL on top of Plug and Bandit. If you need ORMs, generators, live views, or a full asset pipeline, reach for Phoenix. If you want to ship a small HTTP service in an afternoon, you are in the right place.