Hi everyone,
I’ve been working on Francis, a lightweight framework built on top of Plug and Bandit that tries to get you from idea to a running endpoint with as little ceremony as possible. Think Sinatra for Elixir.
What it is
You use Francis in a module, define routes with get/post/ws/sse, and you have a running server. No routers, no controllers, no context boilerplate, just a function per route.
defmodule MyApp do
use Francis
get "/", fn _ -> %{hello: :world} end
get "/:name", fn %{params: %{"name" => name}} -> "hello #{name}" end
post "/", fn conn -> conn.body_params end
ws "/chat", fn
:join, socket -> {:reply, "welcome"}
{:received, msg}, _ -> {:reply, msg}
end
sse "/events", fn
:join, socket -> {:reply, %{id: socket.id}}
{:received, m}, _ -> {:reply, m}
end
unmatched fn _ -> "not found" end
end
Return a string and it renders as HTML. Return a map and it renders as JSON. Return a Plug.Conn for full control.
Get started
mix archive.install hex francis
mix francis.new my_app
cd my_app
mix francis.server
Or add to an existing project:
{:francis, “~> 0.3”}
If you use Claude Code, install the Francis skill for route patterns, SSE/WS gotchas, and security guidance:
npx skills add francis-build/francis@francis-thinking
Links
- Hex: francis | Hex
- Docs: Francis v0.3.3 — Documentation
- Site: https://francis.build
Curious what the community thinks. It is not trying to replace Phoenix, it is for when you want something closer to raw Plug without writing the same boilerplate every time. Happy to answer questions.






















