{"id":10948,"date":"2024-07-14T10:34:41","date_gmt":"2024-07-14T10:34:41","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=10948"},"modified":"2025-08-29T19:45:39","modified_gmt":"2025-08-29T19:45:39","slug":"elixir-and-phoenix","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/elixir-and-phoenix\/","title":{"rendered":"Elixir and Phoenix"},"content":{"rendered":"<p>Most languages which I start to learn, I&#8217;ve found I learn the basics of the language (enough to feel at relative ease with the language) but then want to see it in real world scenarios. One of those is usually a web API or the likes. So today I&#8217;m looking at using Elixir along with the <a href=\"https:\/\/www.phoenixframework.org\/\">Phoenix<\/a> framework.<\/p>\n<p><em>Note: I&#8217;m new to Elixir and Phoenix, this post is based upon my learnings, trying to get a basic web API\/service working and there may be better ways to achieve this that I&#8217;m not aware of yet.<\/em><\/p>\n<p>Phoenix is a way to (as they say on their site) to &#8220;build rich, interactive web applications&#8221;. Actually I find it builds too much as by default it will create a website, code for working with a DB (PostgresQL by default) etc. In this post I want to create something more akin to a web API or microservice.<\/p>\n<p>In this post I want to create a simple API service so instead we&#8217;ll use <a href=\"https:\/\/hexdocs.pm\/phoenix\/Mix.Tasks.Phx.New.html\">phx.new<\/a> to create a service named my_api and well remove the website\/HTML and ecto (the DB) side of things <\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmix phx.new my_api --no-html --no-ecto --no-mailer\r\n<\/pre>\n<p><em>If this fails with a &#8220;not found&#8221; error, try installing phx using &#8220;mix archive.install hex phx_new&#8221;<\/em><\/p>\n<p>If you run the command above you&#8217;ll get a new application generated. Just <em>cd my_api<\/em> to allow us to run the service etc. <\/p>\n<p>If you&#8217;d like to see what the default generated application is then run the following<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmix phx.server\r\n<\/pre>\n<p>By default this will start a server against localhost:4000. If you open the browser you&#8217;ll see a default dashboard\/page which likely says there&#8217;s no route for GET \/ and then lists some available routes.<\/p>\n<p>The \/dev\/dashboard route takes you to a nice LiveDashboard showing information about the Elixir and Phoenix. <\/p>\n<p>To shutdown the Phoenix server CTRL+C twice within the terminal that you ran it up from.<\/p>\n<p>For my very simple web service, I do not even what the live dashboard. So if you created that new app. delete your new app folder and then run this minimal code version (unless you&#8217;d prefer to keep live dashboard etc.)<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmix phx.new my_api --no-html --no-ecto --no-mailer --no-dashboard --no-assets --no-gettext\r\n<\/pre>\n<p>This will then generate a fairly minimal server which is a good starting point for our service. You&#8217;ll notice first off that there are now, no routes when you run this via <em>mix phx.server<br \/>\n<\/em>. <\/p>\n<p>Let&#8217;s add a controller, this will acts as the controller for our web service, so within the \/lib\/my_api_web\/controllers folder add a new file named math-controller.ex and past the following code into it (obviously change the module name to suite your application name)<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndefmodule MyApiWeb.MathController do\r\n  #use MyApiWeb, :controller\r\n  use Phoenix.Controller, formats: &#x5B;:html, :json]\r\n\r\n  def index(conn, _params) do\r\n   json(conn, &quot;{name: Scooby}&quot;)\r\n  end\r\nend\r\n<\/pre>\n<p>We now need to hook up our controller to a route, so go to the router.ex file within the \/lib\/my_api_web\/ folder and alter the scope section to look like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nscope &quot;\/&quot;, MyApiWeb do\r\n  pipe_through :api\r\n\r\n  resources &quot;\/api&quot;, MathController, except: &#x5B;:new, :edit, :create, :delete, :update, :show]\r\nend\r\n<\/pre>\n<p>If you run <em>mix phx.server<\/em> you should see a route to \/api, typing http:\/\/localhost:4000\/api will return &#8220;{name: Scooby}&#8221; as defined in the math-controller index. This is not very math-like so let&#8217;s create a couple of functions, one for adding numbers and one for subtracting.<\/p>\n<p>Remove the resources section (or comment out using #) in the scope then add the following routes<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nget &quot;\/add&quot;, MathController, :add\r\nget &quot;\/sub&quot;, MathController, :subtract\r\n<\/pre>\n<p>Go to the math-controler.ex and add the following functions<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndef add(conn, %{&quot;a&quot; =&gt; a, &quot;b&quot; =&gt; b}) do\r\n  text(conn, String.to_integer(a) + String.to_integer(b))\r\nend\r\n\r\ndef subtract(conn, %{&quot;a&quot; =&gt; a, &quot;b&quot; =&gt; b}) do\r\n  text(conn, String.to_integer(a) - String.to_integer(b))\r\nend\r\n<\/pre>\n<p>Notice we destructuring params to values a and b &#8211; we&#8217;ll convert those values to integers and use the <em>text<\/em> function to return raw text (previously we expected JSON hence uses the <em>json<\/em> function). Now when you browse the add method, for example http:\/\/localhost:4000\/add?a=10&#038;b=5 or subtract method, for example http:\/\/localhost:4000\/sub?a=10&#038;b=5 you should see raw text returned with answers to the math functions.<\/p>\n<p><strong>What routes are we exposing<\/strong><\/p>\n<p>Another useful way of checking the available routes (without running the server) is, as follows<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmix phx.routes\r\n<\/pre>\n<p><strong>Config<\/strong><\/p>\n<p>If you&#8217;ve looked around the generated code you&#8217;ll notice the <em>config<\/em> folder. <\/p>\n<p>One thing you might like to do now is change localhost to 0.0.0.0 so edit dev.exs and replace<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nhttp: &#x5B;ip: {127, 0, 0, 1}, port: 4000],\r\n<\/pre>\n<p>with<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nhttp: &#x5B;ip: {0, 0, 0, 0}, port: 4000],\r\n<\/pre>\n<p>If you do NOT do this and you decide to deploy the dev release to Docker, you&#8217;ll find you cannot access your service from outside of Docker (which ofcourse is quite standard). <\/p>\n<p><strong>Releases<\/strong><\/p>\n<p>Generating a release will precompile any files that can be compiled and allows us to run the server without the source code (as you&#8217;d expect) you will need to tell the compiler what configuration to use, we do that by setting the MIX_ENV like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nexport MIX_ENV=prod\r\n<\/pre>\n<p>(No MIX_ENV environment variable will default dev)<\/p>\n<p>Then running<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmix release\r\n<\/pre>\n<p>This will create and assemble your compiled files to _build\/prod\/rel\/my_api\/bin\/my_api (obviously replacing the last part with your app name). The results of a release build show using<\/p>\n<p><em>Note: Replace \/prod\/ with \/dev\/ above etc. as per the environment you&#8217;ve compiled for<\/em><\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n_build\/prod\/rel\/my_api\/bin\/my_api start\r\n<\/pre>\n<p>to start your application, this will need start a server. By default the above does not start a server so instead we need to set the following environment variable<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nexport PHX_SERVER=true\r\n<\/pre>\n<p>You&#8217;ll also able to run following it will automatically generate the bin\/server and sets the PHX_SERVER environment variable<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmix phx.gen.release\r\n<\/pre>\n<p>One last thing, you may find when you use the <em>start<\/em> command (against PROD) that this fails saying you are missing the SECRET_KEY_BASE. We can generate this using<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nmix phx.gen.secret\r\n<\/pre>\n<p>Then simply <\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nexport SECRET_KEY_BASE=your-generated-key\r\n<\/pre>\n<p>This is for signing cookies etc. and you can see where the exception comes from within the runtime.exs file. This is set as an environment variable, best not to check into source control.<\/p>\n<p><strong>Dockerizing our service<\/strong><\/p>\n<p>Okay, it&#8217;s not Elixir specific, but I feel that the natural conclusion to our API\/service development is to have it all running in a container. Let&#8217;s start by creating a container image based upon the build and using the phx.server call&#8230;<\/p>\n<p>Create yourself a Dockerfile which looks like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nFROM elixir:latest\r\n\r\nRUN mkdir \/app\r\nCOPY . \/app\r\nWORKDIR \/app\r\n\r\nRUN mix local.hex --force\r\nRUN mix do compile\r\n\r\nEXPOSE 4000\r\n\r\nCMD &#x5B;&quot;mix&quot;, &quot;phx.server&quot;]\r\n<\/pre>\n<p>I&#8217;m assuming we&#8217;re going to stick with port 4000 in the above and in the commands below, so I&#8217;ll document this via the EXPOSE command.<\/p>\n<p>Now to build and run our container let&#8217;s use the following<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndocker build -t pp\/my-api:0.1.0 .\r\ndocker run --rm --name my-api -p 4000:4000 -d pp\/my-api:0.1.0\r\n<\/pre>\n<p>Now you should be able to uses http:\/\/localhost:4000 to access your shiny new Elixir\/Phoenix API\/service.<\/p>\n<p><em>Note: Remember that if you cannot access the service outside of the docker image, ensure you&#8217;ve set the http ip in dev.exs to 0.0.0.0<\/em><\/p>\n<p>If we want to instead containerize our release build then we could use the following<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nFROM elixir:latest\r\n\r\nENV PHX_SERVER=true\r\n\r\nRUN mkdir \/app\r\nCOPY \/_build\/dev\/ \/app\r\nWORKDIR \/app\/rel\/my_api\/bin\r\n\r\nEXPOSE 4000\r\n\r\nCMD &#x5B;&quot;.\/my_api&quot;, &quot;start&quot;]\r\n<\/pre>\n<p>Again using the previous build and run commands, will start the server (if all went to plan).<\/p>\n<p><strong>Code<\/strong><\/p>\n<p>Code is available in my GitHub <a href=\"https:\/\/github.com\/putridparrot\/blog-projects\/tree\/master\/Elixir\" rel=\"noopener\" target=\"_blank\">blog project repo<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most languages which I start to learn, I&#8217;ve found I learn the basics of the language (enough to feel at relative ease with the language) but then want to see it in real world scenarios. One of those is usually a web API or the likes. So today I&#8217;m looking at using Elixir along with [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[730,731,356],"tags":[],"class_list":["post-10948","post","type-post","status-publish","format-standard","hentry","category-elixir","category-phoenix","category-web-api"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10948","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=10948"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10948\/revisions"}],"predecessor-version":[{"id":11771,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/10948\/revisions\/11771"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=10948"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=10948"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=10948"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}