Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build your own web framework in Elixir

Build your own web framework in Elixir

Do you use Phoenix, but you don’t know how it works underneath the hood? Do you have a nagging feeling that you should know more about the ins and outs of processing a web request and turning it into a rendered page?

Good, then this talk is for you!

Understanding how a web framework turns a request into a rendered page is an important aspect of building web applications. And what better way to learn the fundamentals of how a web framework works than to build one in Elixir? So join me as we build a web framework in Elixir complete with a router, controllers, and templates. We’ll take a look at how Plug has already done most of the work for us and at how we can leverage such a powerful abstraction to extend what it gives us. We’ll even look at how to make our templates render blazingly fast by compiling them into functions.

You may not be ready to bet your business on this new framework, but by the end of the talk, you’ll certainly be better equipped to understand what Phoenix is doing underneath macros and to extend it with new functionality. And hey, if you really want to create a new framework, you’ll get a solid start.

German Velasco

March 02, 2018
Tweet

More Decks by German Velasco

Other Decks in Programming

Transcript

  1. Build your own web framework
    German Velasco
    @germsvel

    View Slide

  2. thoughtbot

    View Slide

  3. github.com/
    germsvel/daze

    View Slide

  4. Cowboy
    Plug
    EEx

    View Slide

  5. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View Slide

  6. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View Slide

  7. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View Slide

  8. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View Slide

  9. The Basics

    View Slide

  10. mix new daze --sup

    View Slide

  11. config/
    deps/
    lib/
    test/
    mix.exs
    mix.lock

    View Slide

  12. defmodule Daze.Mixfile do
    def application do
    [
    extra_applications: [:logger, :cowboy, :plug],
    mod: {Daze.Application, []}
    ]
    end
    defp deps do
    [
    {:cowboy, "~> 1.1.0"},
    {:plug, "~> 1.4.0"}
    ]
    end
    end

    View Slide

  13. defmodule Daze.Mixfile do
    def application do
    [
    extra_applications: [:logger, :cowboy, :plug],
    mod: {Daze.Application, []}
    ]
    end
    defp deps do
    [
    {:cowboy, "~> 1.1.0"},
    {:plug, "~> 1.4.0"}
    ]
    end
    end

    View Slide

  14. defmodule Daze.Mixfile do
    def application do
    [
    extra_applications: [:logger, :cowboy, :plug],
    mod: {Daze.Application, []}
    ]
    end
    defp deps do
    [
    {:cowboy, "~> 1.1.0"},
    {:plug, "~> 1.4.0"}
    ]
    end
    end

    View Slide

  15. mix deps.get

    View Slide

  16. config/
    deps/
    lib/
    daze/
    application.ex
    test/
    mix.exs
    mix.lock

    View Slide

  17. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View Slide

  18. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, }
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View Slide

  19. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, }
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View Slide

  20. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, options: [port: 4001]}
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View Slide

  21. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, plug: Daze.Router, options: [port: 4001]}
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View Slide

  22. config/
    deps/
    lib/
    daze/
    + router.ex
    test/
    mix.exs
    mix.lock

    View Slide

  23. defmodule Daze.Router do
    end

    View Slide

  24. defmodule Daze.Router do
    use Plug.Router
    end

    View Slide

  25. defmodule Daze.Router do
    use Plug.Router
    plug :match
    plug :dispatch
    end

    View Slide

  26. defmodule Daze.Router do
    use Plug.Router
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View Slide

  27. mix run --no-halt

    View Slide

  28. View Slide

  29. Are we getting requests?

    View Slide

  30. defmodule Daze.Router do
    use Plug.Router
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View Slide

  31. defmodule Daze.Router do
    use Plug.Router
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View Slide

  32. View Slide

  33. mix daze.server
    config/
    deps/
    lib/
    mix/
    tasks/
    + daze.server.ex
    test/
    mix.exs
    mix.lock

    View Slide

  34. defmodule Mix.Tasks.Daze.Server do
    use Mix.Task
    def run(args) do
    Mix.Tasks.Run.run(args ++ ["--no-halt"])
    end
    end

    View Slide

  35. defmodule Mix.Tasks.Daze.Server do
    use Mix.Task
    def run(args) do
    Mix.Tasks.Run.run(args ++ ["--no-halt"])
    end
    end

    View Slide

  36. defmodule Mix.Tasks.Daze.Server do
    use Mix.Task
    require Logger
    def run(args) do
    Logger.info("Starting Daze Server")
    Mix.Tasks.Run.run(args ++ ["--no-halt"])
    end
    end

    View Slide

  37. View Slide

  38. View Slide

  39. defmodule Daze.Router do
    use Plug.Router
    if Mix.env() == :dev do
    use Plug.Debugger
    end
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View Slide

  40. defmodule Daze.Router do
    use Plug.Router
    if Mix.env() == :dev do
    use Plug.Debugger, style: [primary: "#c0392b", accent: "#41B577"]
    end
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View Slide

  41. View Slide

  42. Controllers, Views,
    and Templates

    View Slide

  43. Extract
    controller logic

    View Slide

  44. defmodule Daze.Router do
    use Plug.Router
    # snip
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View Slide

  45. defmodule Daze.Router do
    use Plug.Router
    # snip
    get "/", to: Daze.Actions.Posts.Index
    end

    View Slide

  46. config/
    deps/
    lib/
    daze/
    actions/
    posts/
    + index.ex
    test/
    mix.exs
    mix.lock

    View Slide

  47. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  48. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  49. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  50. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  51. View Slide

  52. Templates with
    EEx

    View Slide

  53. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  54. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  55. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  56. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex"
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  57. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:adjective]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View Slide

  58. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:adjective]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index("coolest"))
    end
    end

    View Slide

  59. config/
    deps/
    lib/
    daze/
    templates/
    posts/
    + index.html.ex
    test/
    mix.exs
    mix.lock

    View Slide






  60. EEx is the <%= adjective %>!


    View Slide






  61. EEx is the <%= adjective %>!


    View Slide

  62. View Slide






  63. EEx is the <%= adjective1 %> and the <%= adjective2 %>!


    View Slide

  64. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:adjective1, :adjective2]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index("coolest", "fastest"))
    end
    end

    View Slide

  65. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View Slide

  66. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View Slide






  67. EEx is the <%= @adjective1 %> and the <%= @adjective2 %>!


    View Slide

  68. View Slide

  69. Views

    View Slide

  70. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View Slide

  71. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    - require EEx
    - EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View Slide

  72. config/
    deps/
    lib/
    daze/
    views/
    + posts.ex
    test/
    mix.exs
    mix.lock

    View Slide

  73. defmodule Daze.Views.Posts do
    end

    View Slide

  74. defmodule Daze.Views.Posts do
    require EEx
    EEx.function_from_file
    :def,
    :index,
    "lib/daze/templates/posts/index.html.eex",
    [:assigns]
    end

    View Slide

  75. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    |> send_resp(200, Posts.index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View Slide

  76. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    |> send_resp(200, Posts.index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View Slide

  77. View Slide

  78. Handling forms

    View Slide






  79. Posts


    Post:

    Author

    Post




    View Slide






  80. Posts


    Post:

    Author

    Post




    View Slide

  81. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    end

    View Slide

  82. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    end

    View Slide

  83. conn # =>
    %Plug.Conn{
    query_params: _,
    body_params: _,
    params: _
    }

    View Slide

  84. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    end

    View Slide

  85. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    post "/posts", to: Daze.Actions.Posts.Create
    end

    View Slide

  86. config/
    deps/
    lib/
    daze/
    actions/
    posts/
    + create.ex
    test/
    mix.exs
    mix.lock

    View Slide

  87. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    end
    end

    View Slide

  88. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    end

    View Slide

  89. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    end
    end

    View Slide

  90. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    end
    end

    View Slide

  91. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    |> put_resp_header("location", url)
    end
    end

    View Slide

  92. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    end
    end

    View Slide

  93. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View Slide

  94. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end

    View Slide

  95. config/
    deps/
    lib/
    daze/
    + repo.ex
    test/
    mix.exs
    mix.lock

    View Slide

  96. defmodule Daze.Repo do
    use Agent
    end

    View Slide

  97. defmodule Daze.Repo do
    use Agent
    def start_link(_) do
    Agent.start_link(fn -> Map.new end, name: __MODULE__)
    end
    end

    View Slide

  98. defmodule Daze.Repo do
    use Agent
    # snip
    def insert(key, value) do
    Agent.update(__MODULE__, fn map ->
    data =
    case Map.get(map, key) do
    nil -> []
    values -> values
    end
    Map.put(map, key, [value | data])
    end)
    end
    end

    View Slide

  99. defmodule Daze.Repo do
    use Agent
    # snip
    def all do
    Agent.get(__MODULE__, fn map ->
    map
    |> Map.values()
    |> Enum.concat()
    end)
    end
    end

    View Slide

  100. defmodule Daze.Application do
    @moduledoc false
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, plug: Daze.Router, options: [port: 4001]},
    {Daze.Repo, []}
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View Slide

  101. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> send_resp(200, Posts.index(posts: posts))
    end
    end

    View Slide



  102. # snip
    <%= if @posts do %>

    <%= for post <- @posts do %>

    <%= post.body %>
    by <%= post.author %>

    <% end %>

    <% end %>



    View Slide



  103. # snip
    <%= if @posts do %>

    <%= for post <- @posts do %>

    <%= post.body %>
    by <%= post.author %>

    <% end %>

    <% end %>



    View Slide



  104. # snip
    <%= if @posts do %>

    <%= for post <- @posts do %>

    <%= post.body %>
    by <%= post.author %>

    <% end %>

    <% end %>



    View Slide

  105. View Slide

  106. View Slide

  107. Daze.Action

    View Slide

  108. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    # snip
    end
    end

    View Slide

  109. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    # snip
    end
    end

    View Slide

  110. config/
    deps/
    lib/
    daze/
    + action.ex
    test/
    mix.exs
    mix.lock

    View Slide

  111. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    end
    end
    end

    View Slide

  112. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    def init(opts), do: opts
    end
    end
    end

    View Slide

  113. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, Posts.index(posts: posts))
    end
    end

    View Slide

  114. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, Posts.index(posts: posts))
    end
    end

    View Slide

  115. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    render(conn, Posts.index(posts: posts))
    end
    def render(conn, body) do
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, body)
    end
    end

    View Slide

  116. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    def init(opts), do: opts
    end
    end
    end

    View Slide

  117. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    end

    View Slide

  118. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    end
    end

    View Slide

  119. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, body)
    end
    end
    end

    View Slide

  120. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    render(conn, Posts.index(posts: posts))
    end
    end

    View Slide

  121. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View Slide

  122. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View Slide

  123. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    redirect(conn, to: "/")
    end
    def redirect(conn, to: url) do
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View Slide

  124. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, body)
    end
    end
    end

    View Slide

  125. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    # snip
    end
    def redirect(conn, to: url) do
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end
    end

    View Slide

  126. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    # snip
    end
    def redirect(conn, to: url) do
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end
    end

    View Slide

  127. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    redirect(conn, to: "/")
    end
    end

    View Slide

  128. Daze.View

    View Slide

  129. defmodule Daze.Views.Posts do
    require EEx
    EEx.function_from_file
    :def,
    :index,
    "lib/daze/templates/posts/index.html.eex",
    [:assigns]
    end

    View Slide

  130. defmodule Daze.Views.Posts do
    require EEx
    EEx.function_from_file
    :def,
    :index,
    "lib/daze/templates/posts/index.html.eex",
    [:assigns]
    end

    View Slide

  131. config/
    deps/
    lib/
    daze/
    + view.ex
    test/
    mix.exs
    mix.lock

    View Slide

  132. defmodule Daze.View do
    @root_path "lib/daze/templates"
    end

    View Slide

  133. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    end
    end

    View Slide

  134. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    end
    end

    View Slide

  135. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def
    )
    end
    end
    end

    View Slide

  136. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def,
    unquote(function_name),
    unquote(template_path)
    )
    end
    end
    end

    View Slide

  137. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def,
    unquote(function_name),
    unquote(template_path),
    [:assigns]
    )
    end
    end
    end

    View Slide

  138. defmodule Daze.View do
    defmacro __using__(_) do
    quote do
    import Daze.View
    end
    end
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def,
    unquote(function_name),
    unquote(template_path),
    [:assigns]
    )
    end
    end
    end

    View Slide

  139. defmodule Daze.Views.Posts do
    use Daze.View
    deftemplate("posts/index.html.eex", :index)
    end

    View Slide

  140. View Slide

  141. Sessions

    View Slide

  142. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    # snip
    end

    View Slide

  143. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session,
    plug :dispatch
    # snip
    end

    View Slide

  144. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session, store: :cookie,
    plug :dispatch
    # snip
    end

    View Slide

  145. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session, store: :cookie, key: "_daze_app",
    plug :dispatch
    # snip
    end

    View Slide

  146. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :dispatch
    # snip
    end

    View Slide

  147. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :dispatch
    # snip
    def put_secret_key_base(conn, _) do
    end
    end

    View Slide

  148. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :dispatch
    # snip
    def put_secret_key_base(conn, _) do
    put_in(
    conn.secret_key_base,
    "tosp2eJ0EVDXq/litnqe3iTMf4oGJuos0PbJk1NzkL4SaMEPB8OGu4T8Ym+WavGY"
    )
    end
    end

    View Slide

  149. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View Slide

  150. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    post "/posts", to: Daze.Actions.Posts.Create
    get "/sign_in", to: Daze.Actions.Sessions.New
    # snip
    end

    View Slide

  151. config/
    deps/
    lib/
    daze/
    actions/
    sessions/
    + new.ex
    test/
    mix.exs
    mix.lock

    View Slide

  152. defmodule Daze.Actions.Sessions.New do
    use Daze.Action
    end

    View Slide

  153. defmodule Daze.Actions.Sessions.New do
    use Daze.Action
    def call(conn, _opts) do
    render(conn, Daze.Views.Sessions.new([]))
    end
    end

    View Slide

  154. config/
    deps/
    lib/
    daze/
    views/
    + sessions.ex
    test/
    mix.exs
    mix.lock

    View Slide

  155. defmodule Daze.Views.Sessions do
    use Daze.View
    deftemplate("sessions/new.html.eex", :new)
    end

    View Slide

  156. config/
    deps/
    lib/
    daze/
    templates/
    sessions/
    + new.html.eex
    test/
    mix.exs
    mix.lock

    View Slide






  157. Sign in


    Email:

    Password:

    Sign in




    View Slide






  158. Sign in


    Email:

    Password:

    Sign in




    View Slide

  159. View Slide

  160. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    post "/posts", to: Daze.Actions.Posts.Create
    get "/sign_in", to: Daze.Actions.Sessions.New
    post "/sessions", to: Daze.Actions.Sessions.Create
    # snip
    end

    View Slide

  161. config/
    deps/
    lib/
    daze/
    actions/
    sessions/
    + create.ex
    test/
    mix.exs
    mix.lock

    View Slide

  162. defmodule Daze.Actions.Sessions.Create do
    use Daze.Action
    end

    View Slide

  163. defmodule Daze.Actions.Sessions.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{"email" => email} = conn.params
    conn
    |> put_session(:current_user_email, email)
    |> redirect(to: "/")
    end
    end

    View Slide

  164. defmodule Daze.Actions.Sessions.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{"email" => email} = conn.params
    conn
    |> put_session(:current_user_email, email)
    |> redirect(to: "/")
    end
    end

    View Slide

  165. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> render(Posts.index(posts: posts))
    end
    end

    View Slide

  166. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    current_user_email = get_session(conn, :current_user_email)
    conn
    |> render(Posts.index(posts: posts))
    end
    end

    View Slide

  167. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    current_user_email = get_session(conn, :current_user_email)
    conn
    |> render(Posts.index(posts: posts, current_user_email: current_user_email))
    end
    end

    View Slide






  168. Posts
    <%= if @current_user_email do %>
    Welcome <%= @current_user_email %>
    <% else %>
    Sign In
    <% end %>
    # snip



    View Slide

  169. View Slide

  170. View Slide

  171. View Slide

  172. Static assets

    View Slide

  173. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View Slide

  174. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug Plug.Static,
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View Slide

  175. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug Plug.Static, from: :daze, at: "/",
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View Slide

  176. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug Plug.Static, from: :daze, at: "/", only: ["images", "css"]
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View Slide






  177. # snip

    View Slide

  178. config/
    deps/
    lib/
    priv/
    static/
    css/
    + app.css
    test/
    mix.exs
    mix.lock

    View Slide

  179. body {
    background-color: #fff;
    font-family: 'Open Sans', sans-serif;
    }
    .container {
    display: flex;
    flex-direction: column;
    }
    .header {
    display: flex;
    flex-flow: row nowrap;
    justify-content: space-evenly;
    align-items: center;
    margin: 4em;
    }
    .banner-image {
    width: 100px;
    }
    .main {
    display: flex;
    flex-flow: column nowrap;
    margin: auto;
    }
    .posts {
    margin: 3em 0.5em;
    }
    .post-item {
    margin: 0 0.5em 3em 0.5em;
    list-style: none;
    }
    .post-body {
    font-size: 1.5em;
    }
    .post-author {
    margin-top: 0.3em;
    display: block;
    text-align: right;
    }

    View Slide










  180. Posts
    # snip



    View Slide

  181. config/
    deps/
    lib/
    priv/
    static/
    images/
    + elixir-daze.svg
    test/
    mix.exs
    mix.lock

    View Slide

  182. Demo

    View Slide

  183. View Slide

  184. Final thoughts

    View Slide

  185. github.com/germsvel/daze
    @germsvel

    View Slide