├── test ├── test_helper.exs └── slim_phoenix_test.exs ├── lib ├── templates │ ├── 404.html.eex │ ├── about.html.eex │ ├── contact.html.eex │ └── home.html.eex ├── slim_phoenix │ ├── conn.ex │ ├── router.ex │ ├── cowboy_handler.ex │ └── application.ex ├── slim_phoenix.ex ├── views │ └── page_view.ex └── controllers │ └── page_controller.ex ├── .gitignore ├── mix.lock ├── mix.exs ├── README.md └── config └── config.exs /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /lib/templates/404.html.eex: -------------------------------------------------------------------------------- 1 |

404 error from template file

2 | -------------------------------------------------------------------------------- /lib/templates/about.html.eex: -------------------------------------------------------------------------------- 1 |

About page from template file

2 | -------------------------------------------------------------------------------- /lib/templates/contact.html.eex: -------------------------------------------------------------------------------- 1 |

Contact page from template file

2 | -------------------------------------------------------------------------------- /lib/templates/home.html.eex: -------------------------------------------------------------------------------- 1 |

Hello <%= name %>, this is homepage from the template file

2 | -------------------------------------------------------------------------------- /test/slim_phoenix_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenixTest do 2 | use ExUnit.Case 3 | doctest SlimPhoenix 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/slim_phoenix/conn.ex: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix.Conn do 2 | defstruct [:req_path, :res_code, :res_body] 3 | 4 | def put_res_body(conn, body) do 5 | Map.put(conn, :res_body, body) 6 | end 7 | 8 | def put_res_code(conn, code) do 9 | Map.put(conn, :res_code, code) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/slim_phoenix.ex: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix do 2 | @moduledoc """ 3 | Documentation for SlimPhoenix. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> SlimPhoenix.hello 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/views/page_view.ex: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix.PageView do 2 | import SlimPhoenix.Conn, only: [put_res_body: 2] 3 | 4 | def render(path, assigns \\ []) 5 | for path <- Path.wildcard("lib/templates/*.eex") do 6 | base_path = Path.basename(path, ".eex") 7 | template_content = File.read!(path) 8 | def unquote(:"render")(unquote(base_path), assigns) do 9 | EEx.eval_string unquote(template_content), assigns 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /lib/slim_phoenix/router.ex: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix.Router do 2 | alias SlimPhoenix.PageController 3 | 4 | def call(conn) do 5 | content_for(conn.req_path, conn) 6 | end 7 | 8 | defp content_for("/", conn) do 9 | conn 10 | |> PageController.home 11 | end 12 | 13 | defp content_for("/contact", conn) do 14 | conn 15 | |> PageController.contact 16 | end 17 | 18 | defp content_for("/about", conn) do 19 | conn 20 | |> PageController.about 21 | end 22 | 23 | defp content_for(_, conn) do 24 | conn 25 | |> PageController.page_not_found 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/slim_phoenix/cowboy_handler.ex: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix.CowboyHandler do 2 | def init(_type, req, opts) do 3 | {:ok, req, opts} 4 | end 5 | 6 | def handle(request, opts) do 7 | [ router | _ ] = opts 8 | { path, request } = :cowboy_req.path request 9 | conn = %SlimPhoenix.Conn{req_path: path} 10 | conn = router.call(conn) 11 | 12 | { :ok, reply } = :cowboy_req.reply( 13 | conn.res_code, 14 | [{"content-type", "text/html"}], 15 | conn.res_body, 16 | request 17 | ) 18 | 19 | {:ok, reply, opts} 20 | end 21 | 22 | def terminate(_reason, _request, _state), do: :ok 23 | end 24 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]}, 2 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, 3 | "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []}, 4 | "plug": {:hex, :plug, "1.3.0", "6e2b01afc5db3fd011ca4a16efd9cb424528c157c30a44a0186bcc92c7b2e8f3", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, 5 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []}} 6 | -------------------------------------------------------------------------------- /lib/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix.PageController do 2 | import SlimPhoenix.Conn, only: [put_res_code: 2, put_res_body: 2] 3 | 4 | def home(conn) do 5 | conn 6 | |> put_res_code(200) 7 | |> render("home.html", name: "Shankardevy") 8 | end 9 | 10 | def about(conn) do 11 | conn 12 | |> put_res_code(200) 13 | |> render("about.html") 14 | end 15 | 16 | def contact(conn) do 17 | conn 18 | |> put_res_code(200) 19 | |> render("contact.html") 20 | end 21 | 22 | def page_not_found(conn) do 23 | conn 24 | |> put_res_code(404) 25 | |> render("404.html") 26 | end 27 | 28 | defp render(conn, template, assigns \\ []) do 29 | data = SlimPhoenix.PageView.render(template, assigns) 30 | conn 31 | |> put_res_body(data) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /lib/slim_phoenix/application.ex: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix.Application do 2 | @moduledoc false 3 | 4 | use Application 5 | 6 | def start(_type, _args) do 7 | import Supervisor.Spec, warn: false 8 | 9 | # This is not the proper way to start the cowboy server in the supervision tree. 10 | # Proper way of starting the cowboy server will be explored later in the book. 11 | # For now, this helps to understand the basic concepts. 12 | dispatch_config = :cowboy_router.compile([ 13 | { :_, # Match any host 14 | [{ :_, SlimPhoenix.CowboyHandler, [SlimPhoenix.Router] }] # Map all path to CowboyHandler 15 | } 16 | ]) 17 | 18 | :cowboy.start_http(:http, 100,[{:port, 8080}],[{ :env, [{:dispatch, dispatch_config}]}]) 19 | 20 | children = [] 21 | opts = [strategy: :one_for_one, name: SlimPhoenix.Supervisor] 22 | Supervisor.start_link(children, opts) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule SlimPhoenix.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :slim_phoenix, 6 | version: "0.1.0", 7 | elixir: "~> 1.4", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | # Specify extra applications you'll use from Erlang/Elixir 18 | [extra_applications: [:logger], 19 | mod: {SlimPhoenix.Application, []}] 20 | end 21 | 22 | # Dependencies can be Hex packages: 23 | # 24 | # {:my_dep, "~> 0.3.0"} 25 | # 26 | # Or git/path repositories: 27 | # 28 | # {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 29 | # 30 | # Type "mix help deps" for more examples and options 31 | defp deps do 32 | [{:cowboy, "~> 1.0.0"}, 33 | {:plug, "~> 1.0"}] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slim Phoenix 2 | 3 | A minimal rebuild of Phoenix to demonstrate various components of Phoenix, Plug, Cowboy. 4 | 5 | The code in this repo is part of my upcoming book on Phoenix. It incrementally builds a simple Phoenix replica starting from using a plain cowboy integration and then building a router, controller, view and template on top of it one at a time. 6 | 7 | If you are curious and can't wait till the book the release, go clone the repo and run it locally. Checkout the repo to the initial commit and look for the changes with each new commit. 8 | 9 | ## How to Run locally 10 | 11 | * Clone this project locally. 12 | * From inside the project directory, run `mix do deps.get` to get dependencies. 13 | * Visit [http://localhost:8080](http://localhost:8080) 14 | * '/' render "lib/templates/home.html.eex" 15 | * '/contact' render "lib/templates/contact.html.eex" 16 | * '/about' render "lib/templates/about.html.eex" 17 | * any other path renders "lib/templates/404.html.eex" 18 | 19 | ## Subscribe 20 | 21 | To get notified when the book is released, please subscribe at [http://shankardevy.github.io/phoenix-book/](http://shankardevy.github.io/phoenix-book/) 22 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :slim_phoenix, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:slim_phoenix, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | --------------------------------------------------------------------------------