Elixir is a functional language… and Phoenix takes it seriously.
The web seems to be a perfect fit for functional languages. You invoke a function passing some arguments and you expect a result, or speaking in web related terms, you make a request to an URL with some parameters and you expect a response.
You can think about Phoenix web applications as if they were big functions. Each request would be a function call taking one URL (a formatted string) as argument, and returning a response (that would also be a formatted string).
The book Programming Elixir 1.3 by Dave Thomas made me see Elixir programs more or less like assembly line factories. These factories transform data, performing one task (or data transformation) on each station. Phoenix is no exception to this philosophy.
The coarse-grained view for a request in Phoenix would be phoenix(connection)
, that is, passing a connection to the phoenix function, doing some transformations on the data, and returning that transformed connection. Representing this with Elixir’s pipe notation, it would look similar to
connection |> phoenix
Phoenix encourages breaking big functions into smaller ones. With our assembly line factory analogy in mind, lets divide our phoenix factory into four big stations:
connection
|> endpoint
|> router
|> pipelines
|> controller
The connection starts its path through the Phoenix world at the endpoint, then it passes through the router, which applies some pipelines to the connection and then routes it to the corresponding controller. Lets see each station in detail.
The Endpoint
The endpoint is the starting point at Phoenix. Phoenix applications usually have one endpoint, although they could have more depending on if you want to have several parts of your application running on different ports or having specific security constraints.
The endpoint applies a chain of plugs at the beginning of each request. This is how it looks like in an application I’ve just created
connection
|> plug Plug.Static.call
|> plug Plug.RequestId.call
|> plug Plug.Logger.call
|> plug Plug.Parsers
|> plug Plug.MethodOverride.call
|> plug Plug.Head.call
|> plug Plug.Session
|> plug Hello.Router.call
As you can see, the common tasks it performs are: serving static files, generating a unique request ID, logging, parsing the request body, overriding the POST method with the method indicated at the _method
request parameter, converting HEAD requests into GET requests, handling session cookies and session stores, and finally, calling the router, which is our next big station.
What is a plug?
The Plug library is a specification to build composable modules in between web applications. Each plug consumes and produces a connection structure called
Plug.Conn
. That is, they receive a connection, change that connection a little bit and pass it forward.For us this mechanism means we can plug in or out functions (data transformations) as needed. For example, in development mode, we would like to have the ability to live reload our templates as soon as we save the file in our editor, that means plugging in
Phoenix.LiveReloader
andPhoenix.CodeReloader
, easy.
The Router
In the router we typically find the pipelines that are defined for this application. By default on a new Phoenix application you would get two: :browser
and :api
. On the other hand you find a routing table like the one below.
The router, based on the scope of the URL it gets, defines which pipeline will be applied to your request, and also to which controller and action it gets routed.
1 2 3 4 5 |
scope "/", Bolt do # Bolt would be the name of the app pipe_through :browser # Use the default browser stack get "/", PageController, :index end |
The Pipelines
A pipeline is like a set of plugs, or a bigger plug made from other plugs, it gets a connection, does something, and finally returns another connection.
Depending on the request’s scope, the router will decide which pipeline should be used, that way you can apply some functions only for specific types of requests. In the example below you can see the pipeline used for browser requests. If your application was a API you would usually only need to ensure it accepts JSON or XML depending on your needs.
1 2 3 4 5 6 7 |
pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end |
The Controller
This is the last big station. Here a request would typically reach the controller, apply some common functions and then call an action.
connection
|> controller
|> common_stuff
|> action
And the action would perform the end task the request was aimed to. In case the action returns some data, it could fetch it, call the view and at the end, present it with a template.
connection
|> fetch_data
|> view
|> template
That’s all folks!
After reading this I hope you appreciate how well functional programming fits to the web. If you liked it and would like more people to read it, please share it on social media.
Bibliography
The content of this post was inspired by the books: