Francis (Francis v0.2.0)
View SourceModule responsible for starting the Francis server and to wrap the Plug functionality
This module performs multiple tasks:
- Uses the Application module to start the Francis server
- Defines the Francis.Router which uses Francis.Plug.Router, :match and :dispatch
- Defines the macros get, post, put, delete, patch and ws to define routes for each operation
- Setups Plug.Static with the given options
- Sets up Plug.Parsers with the default configuration of:
plug(Plug.Parsers, parsers: [:urlencoded, :multipart, :json], json_decoder: Jason )
- Defines a default error handler that returns a 500 status code and a generic error message. You can override this by passing the function name on
:error_handleroption to theuse Francismacro which will override the default error handler.
You can also set the following options:
- :bandit_opts - Options to be passed to Bandit
- :static - Configure Plug.Static to serve static files
- :parser - Overrides the default configuration for Plug.Parsers
- :error_handler - Defines a custom error handler for the server
- :log_level - Sets the log level for Plug.Logger (default is
:info)
Summary
Functions
Defines a DELETE route
Defines a GET route
Retrieves the configuration for a given key, checking both the macro options and the application environment.
Defines a PATCH route
Defines a POST route
Defines a PUT route
Defines an action for umatched routes and returns 404
Defines a WebSocket route with a unified event handler.
Functions
@spec delete(String.t(), (Plug.Conn.t() -> binary() | map() | Plug.Conn.t())) :: Macro.t()
Defines a DELETE route
Examples
defmodule Example.Router do
use Francis
delete "/hello", fn conn ->
"Hello World!"
end
end
@spec get(String.t(), (Plug.Conn.t() -> binary() | map() | Plug.Conn.t())) :: Macro.t()
Defines a GET route
Examples
defmodule Example.Router do
use Francis
get "/hello", fn conn ->
"Hello World!"
end
end
Retrieves the configuration for a given key, checking both the macro options and the application environment.
@spec patch(String.t(), (Plug.Conn.t() -> binary() | map() | Plug.Conn.t())) :: Macro.t()
Defines a PATCH route
Examples
defmodule Example.Router do
use Francis
patch "/hello", fn conn ->
"Hello World!"
end
end
@spec post(String.t(), (Plug.Conn.t() -> binary() | map() | Plug.Conn.t())) :: Macro.t()
Defines a POST route
Examples
defmodule Example.Router do
use Francis
post "/hello", fn conn ->
"Hello World!"
end
end
@spec put(String.t(), (Plug.Conn.t() -> binary() | map() | Plug.Conn.t())) :: Macro.t()
Defines a PUT route
Examples
defmodule Example.Router do
use Francis
put "/hello", fn conn ->
"Hello World!"
end
end
@spec unmatched((Plug.Conn.t() -> binary() | map() | Plug.Conn.t())) :: Macro.t()
Defines an action for umatched routes and returns 404
@spec ws( String.t(), (event :: :join | {:close, term()} | {:received, binary()}, socket :: %{id: binary(), transport: pid(), path: binary(), params: map()} -> {:reply, binary() | map() | {atom(), any()}} | :noreply | :ok), Keyword.t() ) :: Macro.t()
Defines a WebSocket route with a unified event handler.
The handler function uses pattern matching on events, providing an idiomatic Elixir approach. All events flow through a single function with distinct shapes for easy pattern matching.
Events
The handler receives different event types that can be pattern matched:
:join- Sent when a client connects. Return{:reply, message}to send a welcome message.{:close, reason}- Sent when the connection closes. Return:okor:noreply.{:received, message}- Regular WebSocket text messages from the client.
Messages sent via send(socket.transport, message) are automatically forwarded to the client.
Return Values
{:reply, response}- whereresponsecan be a binary, a map, or a list (maps/lists will be JSON encoded):noreplyor:ok- to not send a response
Socket State
The socket state map includes:
:transport- The transport process that can be used to send messages back to the client usingsend/2:id- A unique identifier for the WebSocket connection that can be used to track the connection:path- The actual request path of the WebSocket connection (e.g.,/chat/general):params- A map of path parameters extracted from the route (e.g.,%{"room" => "general"}for route/:room)
Options
:timeout- The timeout for the WebSocket connection in milliseconds (default: 60_000):heartbeat_interval- The interval in milliseconds between ping frames for heartbeat (default: 30_000). Set tonilto disable heartbeat.
Examples
defmodule Example.Router do
use Francis
# Simple echo server
ws "/echo", fn {:received, message}, socket ->
{:reply, message}
end
# Pattern matching on specific messages
ws "/ping", fn {:received, "ping"}, socket ->
{:reply, "pong"}
end
# Full lifecycle handling with pattern matching
ws "/chat/:room", fn
:join, socket ->
room = socket.params["room"]
{:reply, %{type: "welcome", room: room, id: socket.id}}
{:close, reason}, socket ->
Logger.info("Client #{socket.id} left: #{inspect(reason)}")
:ok
{:received, message}, socket ->
room = socket.params["room"]
# Broadcast to self (will be forwarded to client)
send(socket.transport, "Someone said: " <> message)
{:reply, "[" <> room <> "] " <> message}
end
# JSON responses
ws "/json", fn {:received, message}, socket ->
{:reply, %{status: "ok", message: message}}
end
# No reply needed
ws "/fire-and-forget", fn {:received, message}, socket ->
Logger.info("Received: #{message}")
:noreply
end
# Custom heartbeat interval (ping every 10 seconds)
ws "/heartbeat", fn {:received, message}, socket ->
{:reply, message}
end, heartbeat_interval: 10_000
# Disable heartbeat
ws "/no-heartbeat", fn {:received, message}, socket ->
{:reply, message}
end, heartbeat_interval: nil
end