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.

A19876f5694283f26a061746ba82c3b0?s=128

German Velasco

March 02, 2018
Tweet

Transcript

  1. Build your own web framework German Velasco @germsvel

  2. thoughtbot

  3. github.com/ germsvel/daze

  4. Cowboy Plug EEx

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

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

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

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

    call(conn, _opts) do conn end end
  9. The Basics

  10. mix new daze --sup

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

  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
  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
  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
  15. mix deps.get

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

  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
  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
  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
  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
  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
  22. config/ deps/ lib/ daze/ + router.ex test/ mix.exs mix.lock

  23. defmodule Daze.Router do end

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

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

  26. defmodule Daze.Router do use Plug.Router plug :match plug :dispatch get

    "/" do send_resp(conn, 200, "We have a router!") end end
  27. mix run --no-halt

  28. None
  29. Are we getting requests?

  30. defmodule Daze.Router do use Plug.Router plug :match plug :dispatch get

    "/" do send_resp(conn, 200, "We have a router!") end end
  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
  32. None
  33. mix daze.server config/ deps/ lib/ mix/ tasks/ + daze.server.ex test/

    mix.exs mix.lock
  34. defmodule Mix.Tasks.Daze.Server do use Mix.Task def run(args) do Mix.Tasks.Run.run(args ++

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

    ["--no-halt"]) end end
  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
  37. None
  38. None
  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
  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
  41. None
  42. Controllers, Views, and Templates

  43. Extract controller logic

  44. defmodule Daze.Router do use Plug.Router # snip get "/" do

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

    Daze.Actions.Posts.Index end
  46. config/ deps/ lib/ daze/ actions/ posts/ + index.ex test/ mix.exs

    mix.lock
  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
  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
  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
  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
  51. None
  52. Templates with EEx

  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
  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
  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
  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
  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
  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
  59. config/ deps/ lib/ daze/ templates/ posts/ + index.html.ex test/ mix.exs

    mix.lock
  60. <!DOCTYPE html> <html> <head> </head> <body> EEx is the <%=

    adjective %>! </body> </html>
  61. <!DOCTYPE html> <html> <head> </head> <body> EEx is the <%=

    adjective %>! </body> </html>
  62. None
  63. <!DOCTYPE html> <html> <head> </head> <body> EEx is the <%=

    adjective1 %> and the <%= adjective2 %>! </body> </html>
  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
  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
  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
  67. <!DOCTYPE html> <html> <head> </head> <body> EEx is the <%=

    @adjective1 %> and the <%= @adjective2 %>! </body> </html>
  68. None
  69. Views

  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
  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
  72. config/ deps/ lib/ daze/ views/ + posts.ex test/ mix.exs mix.lock

  73. defmodule Daze.Views.Posts do end

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

    end
  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
  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
  77. None
  78. Handling forms

  79. <!DOCTYPE html> <html> <head> </head> <body> <h1>Posts</h1> <div> <form action="/posts"

    method="post"> <label>Post:</label> <textarea name="post"></textarea> <label>Author</label> <input type="text" name="author"> <button type="submit">Post</button> </form> </div> </body> </html>
  80. <!DOCTYPE html> <html> <head> </head> <body> <h1>Posts</h1> <div> <form action="/posts"

    method="post"> <label>Post:</label> <textarea name="post"></textarea> <label>Author</label> <input type="text" name="author"> <button type="submit">Post</button> </form> </div> </body> </html>
  81. defmodule Daze.Router do use Plug.Router # snip plug Plug.Logger plug

    :match plug :dispatch get "/", to: Daze.Actions.Posts.Index end
  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
  83. conn # => %Plug.Conn{ query_params: _, body_params: _, params: _

    }
  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
  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
  86. config/ deps/ lib/ daze/ actions/ posts/ + create.ex test/ mix.exs

    mix.lock
  87. defmodule Daze.Actions.Posts.Create do import Plug.Conn def init(opts), do: opts def

    call(conn, _opts) do end end
  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
  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
  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
  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
  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
  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
  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
  95. config/ deps/ lib/ daze/ + repo.ex test/ mix.exs mix.lock

  96. defmodule Daze.Repo do use Agent end

  97. defmodule Daze.Repo do use Agent def start_link(_) do Agent.start_link(fn ->

    Map.new end, name: __MODULE__) end end
  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
  99. defmodule Daze.Repo do use Agent # snip def all do

    Agent.get(__MODULE__, fn map -> map |> Map.values() |> Enum.concat() end) end end
  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
  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
  102. <html> <body> # snip <%= if @posts do %> <ul>

    <%= for post <- @posts do %> <li> <%= post.body %> <strong>by <%= post.author %></strong> </li> <% end %> </ul> <% end %> </div> </body> </html>
  103. <html> <body> # snip <%= if @posts do %> <ul>

    <%= for post <- @posts do %> <li> <%= post.body %> <strong>by <%= post.author %></strong> </li> <% end %> </ul> <% end %> </div> </body> </html>
  104. <html> <body> # snip <%= if @posts do %> <ul>

    <%= for post <- @posts do %> <li> <%= post.body %> <strong>by <%= post.author %></strong> </li> <% end %> </ul> <% end %> </div> </body> </html>
  105. None
  106. None
  107. Daze.Action

  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
  109. defmodule Daze.Actions.Posts.Create do import Plug.Conn def init(opts), do: opts def

    call(conn, _opts) do # snip end end
  110. config/ deps/ lib/ daze/ + action.ex test/ mix.exs mix.lock

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

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

    def init(opts), do: opts end end end
  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
  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
  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
  116. defmodule Daze.Action do defmacro __using__(opts) do quote do import Plug.Conn

    def init(opts), do: opts end end end
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  128. Daze.View

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

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

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

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

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

    end
  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
  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
  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
  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
  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
  139. defmodule Daze.Views.Posts do use Daze.View deftemplate("posts/index.html.eex", :index) end

  140. None
  141. Sessions

  142. defmodule Daze.Router do use Plug.Router # snip plug Plug.Logger plug

    :match plug Plug.Parsers, parsers: [:urlencoded, :multipart] plug :dispatch # snip end
  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
  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
  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
  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
  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
  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
  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
  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
  151. config/ deps/ lib/ daze/ actions/ sessions/ + new.ex test/ mix.exs

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

  153. defmodule Daze.Actions.Sessions.New do use Daze.Action def call(conn, _opts) do render(conn,

    Daze.Views.Sessions.new([])) end end
  154. config/ deps/ lib/ daze/ views/ + sessions.ex test/ mix.exs mix.lock

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

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

    mix.lock
  157. <!DOCTYPE html> <html> <head> </head> <body> <h1>Sign in</h1> <div> <form

    action="/sessions" method="post"> <label>Email:</label> <input type="email" name="email"> <label>Password:</label> <input type="password" name="password"> <button type="submit">Sign in</button> </form> </div> </body> </html>
  158. <!DOCTYPE html> <html> <head> </head> <body> <h1>Sign in</h1> <div> <form

    action="/sessions" method="post"> <label>Email:</label> <input type="email" name="email"> <label>Password:</label> <input type="password" name="password"> <button type="submit">Sign in</button> </form> </div> </body> </html>
  159. None
  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
  161. config/ deps/ lib/ daze/ actions/ sessions/ + create.ex test/ mix.exs

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

  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
  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
  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
  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
  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
  168. <!DOCTYPE html> <html> <head> </head> <body> <h1>Posts</h1> <%= if @current_user_email

    do %> Welcome <%= @current_user_email %> <% else %> <a href="/sign_in">Sign In</a> <% end %> # snip </div> </body> </html>
  169. None
  170. None
  171. None
  172. Static assets

  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
  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
  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
  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
  177. <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="css/app.css"> </head> # snip

    </html>
  178. config/ deps/ lib/ priv/ static/ css/ + app.css test/ mix.exs

    mix.lock
  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; }
  180. <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="css/app.css"> </head> <body> <div

    class="container"> <div class="header"> <img class="banner-image" src="images/elixir-daze.svg"> <h1>Posts</h1> # snip </div> </body> </html>
  181. config/ deps/ lib/ priv/ static/ images/ + elixir-daze.svg test/ mix.exs

    mix.lock
  182. Demo

  183. None
  184. Final thoughts

  185. github.com/germsvel/daze @germsvel