Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Phoenix 1.7

reading/phoenix_1.7.livemd

Phoenix 1.7

Mix.install([
  {:jason, "~> 1.4"},
  {:kino, "~> 0.9", override: true},
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"}
])

Navigation

Home Report An Issue SQL DrillsWeb Servers

Review Questions

  • What are body params, url params, and query params in an HTTP request?
  • What is the conn?
  • What are the layers of a Phoenix application?

Overview

Phoenix 1.7

Phoenix 1.7 introduced several significant changes compared with Phoenix 1.6. See the Release Announcement for a full overview of changes made. One of the most significant changes was Replacing Phoenix.View with Phoenix.Component. In addition, Phoenix now comes with Tailwind out of the box.

Phoenix 1.7 also introduced Verified Routes using the ~p sigil. These routes replace Path Helpers.

~p"/home"

Many projects in the Elixir ecosystem will still use Phoenix 1.6 or older. While this course focuses on Phoenix 1.7, consider reading the previous Phoenix 1.6 section to learn more about the older version.

Phoenix

The Phoenix Framework is the most popular web development framework for Elixir. Using Phoenix, we can build rich interactive and real-time web applications quickly.

Chris McCord, the creator of Phoenix, has an excellent video to demonstrate the power of Phoenix. You may follow along and build a Twitter clone application in only 15 minutes.

YouTube.new("https://www.youtube.com/watch?v=MZvmYaFkNJI")

The video above uses Phoenix LiveView to create interactive and real-time features. We will cover LiveView in a future lesson.

Model View Controller (MVC) Architecture

Phoenix is heavily influenced by MVC architecture, where an application is broken into several layers using Model-View-Controller (MVC) architecture.

  • Model: Manages the data and business logic of the application.
  • View: Represents visual information.
  • Controller: Handles requests and manipulates the model/view to respond to the user.

More recently, Phoenix has been breaking away from strict MVC architecture, but understanding this style of architecture will help us better understand the overall design choices behind Phoenix.

test

> source: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

Plug

Under the hood, Phoenix uses the Plug specification for composing a web application with functions. Plugs are functions that transform a conn (connection) data structure.

flowchart LR
subgraph Input
  CP[conn, params]
end
subgraph Output
  C[conn]
end
Output[conn]
P[Plug Function]
Input --> P --> Output

Phoenix sets up a pipeline of plugs and uses the transformed conn Plug.Conn struct to return a response to the client.

Plug uses Cowboy, a small and fast HTTP web server. Cowboy uses Ranch to manage the underlying TCP connections for the web server.

flowchart TB
P[Phoenix]
PG[Plug]
C[Cowboy]
R[Ranch]
P --> PG --> C --> R

click P "https://hexdocs.pm/phoenix/overview.html"
click PG "https://hexdocs.pm/plug/readme.html"
click C "https://github.com/ninenines/cowboy"
click R "https://github.com/ninenines/ranch"

Layered Architecture

Phoenix breaks the complexity of our application into several layers with different responsibilities. Separating an application into layers simplifies reasoning about and collaborating on complex applications.

Phoenix generates the following layers of our architecture by default.

  • Endpoint: The boundary layer of the application.
  • Router: Routes the request to the correct controller.
  • Controller: Handles the request—generally, the controller delegates to the Model and View to manipulate business logic and return a response.
  • Context: a module that defines functions related to a specific part of your application, such as users or products. It helps to organize and simplify your code by separating concerns and making it easier to maintain.
  • Schema: A schema maps the database data into an Elixir struct.
  • Migration: Migrations are used to make changes to the database schema over time, such as adding, removing, or altering tables.
  • Component (Previously the View in 1.6): Handles returning the response and may delegate to a template.
  • Template: Builds a response, typically using HEEx (HTML + Embedded Elixir) to build an HTML web page.

Project Structure

By default, Phoenix 1.7 contains the following project folder structure where app is the name of your application.

├── _build
├── assets
├── config
├── deps
├── lib
│   ├── app
│   ├── app.ex
│   ├── app_web
│   └── app_web.ex
├── priv
├── test
├── formatter.exs
├── .gitignore
├── mix.exs
├── mix.lock
└── README.md

Phoenix projects use Mix so this folder structure should feel somewhat familiar. Here’s a breakdown of each file/folder.

  • _build: Build artifacts, such as compiled .beam code.
  • assets: Static assets, such as JavaScript and CSS.
  • config: Configuration files for the application, such as the database configuration and environment-specific settings.
  • deps: Compiled project dependencies, which Mix manages.
  • lib: The application’s source code.
    • app: The business logic for the application, organized into contexts.
    • app.ex: The main application module, which starts the web server and configures the application.
    • app_web: The web-specific code for the application, such as controllers, components, and templates.
    • app_web.ex: The main web module, which configures the Phoenix application.
  • priv: The “priv” directory in Phoenix contains resources needed for production that are not part of the source code, including static files such as images and fonts and generated assets from the “assets” directory.
  • test: Tests for the application.
  • formatter.exs: Formatter configuration.
  • .gitignore: Configuration for files that should be ignored in .git.
  • mix.exs: Configures the application’s dependencies and other settings for the Mix project.
  • mix.lock: The exact versions of the dependencies used in the application, generated by Mix.
  • README.md: Contains information about the project, such as installation instructions or documentation.

Web Project Structure

Requests flow through the Plug Pipeline. The router, controller, component, and template each play an important role in returning a response to the user request.

Router

Phoenix generates a router.ex file that configures the URLs that clients can send HTTP requests to. See Routing documentation for a full guide.

The Phoenix router uses the pipeline/2 macro to setup a plug pipeline.

pipeline :browser do
  plug :accepts, ["html"]
  plug :fetch_session
  plug :fetch_live_flash
  plug :put_root_layout, {AppWeb.LayoutView, :root}
  plug :protect_from_forgery
  plug :put_secure_browser_headers
end

Then the [scope/2] macro groups related routes together under a common base URL. Inside of the scope, the get/4, post/4, put/4, patch/4, and delete/4 macros can handle a specific HTTP request and delegate to a controller to return a response.

scope "/", HelloWeb do
  pipe_through :browser

  get "/", PageController, :home
end

Controller

Controllers in the app_web/controllers manage the response to the HTTP request.

defmodule AppWeb.PageController do
  use AppWeb, :controller

  def home(conn, _params) do
    render(conn, :home)
  end
end

The controller delegates to a function component in a corresponding *HTML component module to return a HEEx template. The atom :home above corresponds to the function home/1 called in the component module.

Component

The *HTML component returns a HEEx template using sigil ~H.

defmodule AppWeb.PageHTML do
  use AppWeb, :html

  def home(assigns) do
    ~H"""
    Hello World
    """
  end
end

Or embeds template files contained in a folder using the embed_templates/2 macro from Phoenix.Component.

defmodule AppWeb.PageHTML do
  use AppWeb, :html

  embed_templates "page_html/*"
end

Templates

The template contains the HEEx code used to build an HTML response. For example, the following might be contained in app_web/page_html/home.html.heex.

Hello, World!

Conn

In a Phoenix app, “conn” stands for “connection”. It represents the connection between the client and the web server, and contains information about the HTTP request and response.

The conn struct is a central concept in Phoenix and is used extensively throughout the framework. It is created when a client sends a request to the server and is passed around between the various parts of the application as the request is processed.

The conn struct contains information such as the HTTP method, the request path, request headers, request body, and response headers. It also includes information about the client’s session and any authentication information, among other things.

Assigns

The Plug.Conn stores an assigns map that is injected into the HEEx template. Values in the assigns map can be bound by providing a keyword list as the third argument of the Controller.render/3 function.

defmodule AppWeb.PageController do
  use AppWeb, :controller

  def home(conn, _params) do
    render(conn, :home, message: "Hello, World!", injected_class: "text-2xl")
  end
end

We can use <%= %> syntax to embed Elixir and use the assigns provided in the template file. Alternatively, we can use curly brackets {} to embed elixir in an HTML attribute.

<p><%= assigns.message %></p>

@ is a shorthand for assigns..

<p><%= @message %></p>

Layouts

The :put_root_layout plug in the :browser pipeline in router.ex sets up layout HEEx templates that are rendered on every page.

pipeline :browser do
  plug :accepts, ["html"]
  plug :fetch_session
  plug :fetch_live_flash
  plug :put_root_layout, {AppWeb.LayoutView, :root} # sets up root layout
  plug :protect_from_forgery
  plug :put_secure_browser_headers
end

The root layout in components/layouts/root.html.heex is a template used by LiveView and regular views that contains the basic structure of an HTML document and any global styles or scripts. It ensures consistency across all pages of your application.

The app layout in components/layouts/app.html.heex is the standard layout for regular HTTP requests and LiveViews.

See LiveLayouts for more information on layouts and their use.

Phoenix Components

Phoenix 1.7 allows us to use function components in both LiveViews and Controller views.

Phoenix generates some useful function components in the app_web/components/core_components.ex file and the Phoenix.Component module defines components such as link/1 and form/1.

<.link navigate={~p"/"}>Home

We can create our own function components. A function component takes the assigns parameter, and returns a HEEx template. We can optionally define attributes with the attr macro.

attr :name, :string

def hello(assigns) do
  ~H"""
  

<%= @name %>

"""
end

Embedded Elixir

We can embed Elixir in our template files using the following syntax.

<%= expression %>

Where expression is our Elixir code, for example:

<%= 1 + 1 %>

We can write any Elixir expression. So, for example, we could write an if statement to render different HTML depending on some condition.

<%= if DateTime.utc_now().hour > 12 do %>
  <p>Good afternoon!</p>
<% else %>
  <p>Good morning!</p>
<% end %>

Or a loop using the for comprehension. Often we use this to create multiple elements based on a collection.

<%= for int <- 1..10 do %>
  <p><%= int %></p>
<% end %>

Notice that all expressions that output a value use the = symbol. Expressions that don’t output a value (or continue the current expression) omit the = symbol.

Here’s a quick tip, we canIO.inspect/2 values in the page without injecting them into the HTML.

<% IO.inspect(@value) %>

Or, to view them in the HTML as a string, you can use Kernel.inspect/2.

<%= inspect(@value) %>

Controller Params

Controllers actions accept params as the second argument. params is a combined map of comma-separated query params, the body of the request, and dynamic values in the URL.

For example, if we sent a post request to the following URL:

http://localhost:4000/1/?param1=some_value,param2=some_value

With the following data in the POST body:

%{body_param: "value"}

Assuming we had the following route defined.


post "/:id", PageController, :home

Then the params in the controller would be bound to:

%{
  "body_param" => "value",
  "id" => "1",
  "param1" => "some_value",
  "param2" => "some_value"
}

Further Reading

For more on Phoenix, consider the following resources.

Commit Your Progress

DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.

Run git status to ensure there are no undesirable changes. Then run the following in your command line from the curriculum folder to commit your progress.

$ git add .
$ git commit -m "finish Phoenix 1.7 reading"
$ git push

We’re proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.

We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.

Navigation

Home Report An Issue SQL DrillsWeb Servers