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. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. defmodule Daze.Router do use Plug.Router plug :match plug :dispatch get

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

    "/" do send_resp(conn, 200, "We have a router!") end end
  11. 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
  12. 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
  13. 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
  14. 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
  15. defmodule Daze.Router do use Plug.Router # snip get "/" do

    send_resp(conn, 200, "We have a router!") end end
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. <!DOCTYPE html> <html> <head> </head> <body> EEx is the <%=

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

    @adjective1 %> and the <%= @adjective2 %>! </body> </html>
  31. 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
  32. 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
  33. 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
  34. 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
  35. <!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>
  36. <!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>
  37. defmodule Daze.Router do use Plug.Router # snip plug Plug.Logger plug

    :match plug :dispatch get "/", to: Daze.Actions.Posts.Index end
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. defmodule Daze.Repo do use Agent # snip def all do

    Agent.get(__MODULE__, fn map -> map |> Map.values() |> Enum.concat() end) end end
  50. 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
  51. 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
  52. <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>
  53. <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>
  54. <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>
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. defmodule Daze.Router do use Plug.Router # snip plug Plug.Logger plug

    :match plug Plug.Parsers, parsers: [:urlencoded, :multipart] plug :dispatch # snip end
  74. 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
  75. 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
  76. 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
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. <!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>
  83. <!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>
  84. 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
  85. 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
  86. 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
  87. 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
  88. 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
  89. 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
  90. <!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>
  91. 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
  92. 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
  93. 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
  94. 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
  95. 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; }
  96. <!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>