<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Manjunath Reddy]]></title><description><![CDATA[Manjunath Reddy]]></description><link>https://manju.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 12 May 2026 16:20:10 GMT</lastBuildDate><atom:link href="https://manju.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[REST API: How to avoid duplicate resource creation on concurrent requests.]]></title><description><![CDATA[Source: http://www.juimg.com/shiliang/201904/anniutubiao_1768500.html
As anyone working as a software engineer, we all might have come across dealing with API's in our lives. It's easy to consume them, however, designing one requires lots of experien...]]></description><link>https://manju.dev/rest-api-how-to-avoid-duplicate-resource-creation-on-concurrent-requests</link><guid isPermaLink="true">https://manju.dev/rest-api-how-to-avoid-duplicate-resource-creation-on-concurrent-requests</guid><category><![CDATA[REST API]]></category><category><![CDATA[idempotence]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[mutex]]></category><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Manjunath Reddy]]></dc:creator><pubDate>Sun, 01 Jan 2023 16:03:35 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://miro.medium.com/max/700/1*a4Bj6pHFnuaGkJ1ZqEPEyQ.png" alt /></p>
<p>Source: <a target="_blank" href="http://www.juimg.com/shiliang/201904/anniutubiao_1768500.html">http://www.juimg.com/shiliang/201904/anniutubiao_1768500.html</a></p>
<p>As anyone working as a software engineer, we all might have come across dealing with API's in our lives. It's easy to consume them, however, designing one requires lots of experience and requires diligent effort and rigorous testing. Also, one of the topics which are always ambiguous in API design is Idempotency, which HTTP verbs are considered to be Idempotent, and which are not, and having that knowledge helps build effective APIs.</p>
<p>We are not going to delve into the Idempotency topic today, the topic is for another day :-). If you would like to explore further, here are some interesting articles</p>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Idempotent">https://developer.mozilla.org/en-US/docs/Glossary/Idempotent</a></p>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Idempotent">https://developer.mozilla.org/en-US/docs/Glossary/Idempotent</a></p>
<h2 id="heading-preamble">Preamble</h2>
<p>In part of this write-up, I'm going to explore a problem I ran into dealing with concurrent requests which resulted in <strong>creating multiple resources with the same data, e</strong>specially since such a problem is very critical if we are dealing with payments.</p>
<h2 id="heading-problem">Problem</h2>
<p>I was working on an integration with external payment gateway providers with their API for accepting payments into our system. As part of the gateway integration, we needed to provide a callback URL for the payment provider to a callback for the payment transaction state (was it successful, failed, rejected etc). End-to-end integration went well and we covered the happy path of accepting payment with all statuses.</p>
<p>However, At one point, we needed to test a transaction which was successful from the client side, and break the connection so that we did not get an immediate callback from the payment provider. Usually in such scenarios, Payment providers eventually call back at certain intervals to make sure our sever state for the transactions is processed correctly, and update the payment status accordingly. At a certain interval, we did get a callback from the payment provider, however, to our surprise, we got multiple callbacks for the same successful payment payload.</p>
<p>In the application, we end up having multiple records for the same payment and ended up updating users' payments multiple times (the number of times we got the callback requests). Imagine, the user wants to topup $100 and ended up having $300 in their wallet. In such scenarios, we can not possibly control external or third-party APIs and how they are designed. They might have retried or used a queue mechanism or reposted the resources accidentally.</p>
<p>Dealing with such a design will be crucial if we are dealing with payments or mission-critical applications.</p>
<h3 id="heading-problem-in-detail">Problem in detail</h3>
<p>Let’s take an example of an application integrating with a payment gateway. Each customer who comes to our app uses to select the payment gateway of their choice to pay for a subscription. Internally in our API, we send a request to the payment gateway to create a new payment (<code>POST /api/payments/payment</code>). If the transaction is successful, we get status successful status from the Payment gateway with transaction details, and the transaction is recorded in our DB. This is the sweet, ideal scenario as shown in the below illustration.</p>
<p><img src="https://miro.medium.com/max/700/0*yZfLDScBpWvSRj2u.png" alt /></p>
<p>On the other hand, if we encounter any network issues while waiting for the payment gateway to acknowledge us, there is no way to find out if the payment was successful or not as illustrated below.</p>
<p><img src="https://miro.medium.com/max/700/0*IAI_LXie2m7La5ex.png" alt /></p>
<p>After a while, the said payment gateway decided to callback our API to update the broken payment transaction. These calls could come in many means, could be by manually requesting the payment status or retries from the payment gateway itself or automatic callbacks. And if we do not have a proper method to check for duplicate transaction records in our database, we might end up updating customer payment as many times as we got the request from the gateway. How do we make sure we do not capture multiple payment records for the same transaction?</p>
<blockquote>
<p>There is always more than one right way.</p>
</blockquote>
<h3 id="heading-solutions">Solutions</h3>
<p>Ideally, most of us already know how to deal with such operations. I will try to elaborate on the methods I applied and the outcome of each method and why I choose to go with a certain method.</p>
<h3 id="heading-method-1-data-validation">Method 1: Data Validation</h3>
<p>Customer payment transactions are recorded in a table called <code>payments</code>. The structure of the table looks like</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-string">`payments`</span> (
  <span class="hljs-string">`id`</span> <span class="hljs-built_in">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> AUTO_INCREMENT,
  <span class="hljs-string">`user_id`</span> <span class="hljs-built_in">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`order_id`</span> <span class="hljs-built_in">varchar</span>(<span class="hljs-number">50</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">''</span>,
  <span class="hljs-string">`status`</span> <span class="hljs-built_in">varchar</span>(<span class="hljs-number">10</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">''</span>,
  <span class="hljs-string">`amount`</span> <span class="hljs-built_in">decimal</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`transaction_id`</span> <span class="hljs-built_in">varchar</span>(<span class="hljs-number">50</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-literal">NULL</span>,
  <span class="hljs-string">`gateway_response`</span> <span class="hljs-keyword">json</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-literal">NULL</span>,
  PRIMARY <span class="hljs-keyword">KEY</span> (<span class="hljs-string">`id`</span>)
) <span class="hljs-keyword">ENGINE</span>=<span class="hljs-keyword">InnoDB</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CHARSET</span>=latin1;
</code></pre>
<p>Each time get a response or callback from the payment gateway, query the DB to check if there is an existing transaction for the same user with similar <code>order_id</code></p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">from</span> payments <span class="hljs-keyword">where</span> user_id = ? <span class="hljs-keyword">and</span> order_id = ?;
</code></pre>
<p>Pseudocode for the payment validation looks like</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> payment = getPayment(userId, orderId);
<span class="hljs-keyword">if</span> (payment) {
  print <span class="hljs-string">'Payment already exists!. Must be duplicate payment'</span>
  <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'duplicate'</span>) or <span class="hljs-keyword">return</span>;
}<span class="hljs-comment">// elsevar newPayment = createPayment(userId, reqParams)</span>
</code></pre>
<p>The problem with this method is that, when we are swamped with concurrent requests with the same payload in a matter of seconds, we still can not avoid duplication. Because it takes time to query and get the response from DB and during that period, we could have created multiple payment records already.</p>
<h3 id="heading-method-2-locking-mutex-mechanism">Method 2: Locking (Mutex) Mechanism</h3>
<p>According to Wikipedia's definition</p>
<blockquote>
<p>In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive: a mechanism that enforces limits on access to a resource when there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy, and with a variety of possible methods, there exist multiple unique implementations for different applications. - <a target="_blank" href="https://en.wikipedia.org/wiki/Lock_(computer_science)">Wikipedia</a></p>
</blockquote>
<p>It is a well-known mechanism that certainly could be applied in many cases, however, not when we are dealing with concurrent requests.</p>
<p>Implementation is quite similar to the above method, however, instead of DB, we could choose to implement this in in-memory data stores such as Redis.</p>
<p>Pseudocode for the locking the payment to avoid duplication looks like</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// make a unique reference key for each payment transaction</span>
<span class="hljs-keyword">var</span> paymentKey = <span class="hljs-string">'PAYMENT'</span> + user_id + order_id
<span class="hljs-keyword">var</span> payment = getPayment(paymentKey) <span class="hljs-comment">// assume that this method calls redis or any other in-memory store to get the key</span>
<span class="hljs-keyword">if</span> (payment) {
  print <span class="hljs-string">'Payment already exists!. Must be duplicate payment'</span>
  <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'duplicate'</span>) or <span class="hljs-keyword">return</span>;
}setPayment(paymentKey) <span class="hljs-comment">// assume that this methods sets the new payment reference in in-memory</span>
<span class="hljs-keyword">var</span> newPayment = createPayment(userId, reqParams)
</code></pre>
<p>From the above pseudocode, when we get the first request, we set the reference in in-memory and create the payment record. And for the subsequent payment records for the same transaction, we ignore them.</p>
<p>Unfortunately, even with this method, a query to in-memory was slow enough to record multiple payment records and still could not avoid duplicates</p>
<p>Here are a few interesting articles regarding locking mechanisms to explore.</p>
<p><a target="_blank" href="https://stackoverflow.com/questions/129329/optimistic-vs-pessimistic-locking">https://stackoverflow.com/questions/129329/optimistic-vs-pessimistic-locking</a> <a target="_blank" href="https://blog.couchbase.com/optimistic-or-pessimistic-locking-which-one-should-you-pick/">https://blog.couchbase.com/optimistic-or-pessimistic-locking-which-one-should-you-pick/</a></p>
<h3 id="heading-method-3-queuing">Method 3: Queuing</h3>
<p>I did not try this approach since I did not have any queues implemented in the application. However, this could be a more reliable method and gives more flexibility for the application to deal with concurrent requests.</p>
<p>The idea here is to queue all the incoming requests into a queue and deal with them slowly using a consumer and validate each incoming request to make sure we capture only one request.</p>
<h3 id="heading-method-4-database-table-with-composite-unique-constrain">Method 4: Database table with composite UNIQUE constrain</h3>
<p>In this method, we design the payments table with a composite unique key that ensures we have a unique record for each payment. The table design is as follows</p>
<pre><code class="lang-javascript">CREATE TABLE <span class="hljs-string">`payments`</span> (
  <span class="hljs-string">`id`</span> int(<span class="hljs-number">11</span>) unsigned NOT NULL AUTO_INCREMENT,
  <span class="hljs-string">`user_id`</span> int(<span class="hljs-number">11</span>) NOT NULL,
  <span class="hljs-string">`order_id`</span> varchar(<span class="hljs-number">50</span>) NOT NULL DEFAULT <span class="hljs-string">''</span>,
  <span class="hljs-string">`status`</span> varchar(<span class="hljs-number">10</span>) NOT NULL DEFAULT <span class="hljs-string">''</span>,
  <span class="hljs-string">`amount`</span> decimal DEFAULT NULL,
  <span class="hljs-string">`transaction_id`</span> varchar(<span class="hljs-number">50</span>) DEFAULT NULL,
  <span class="hljs-string">`gateway_response`</span> json DEFAULT NULL,
  PRIMARY KEY (<span class="hljs-string">`id`</span>),
  UNIQUE KEY <span class="hljs-string">`unique_payment_transaction`</span> (<span class="hljs-string">`user_id`</span>,<span class="hljs-string">`order_id`</span>,<span class="hljs-string">`status`</span>),
  KEY <span class="hljs-string">`user_id`</span> (<span class="hljs-string">`user_id`</span>),
  KEY <span class="hljs-string">`order_id`</span> (<span class="hljs-string">`order_id`</span>)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
</code></pre>
<p>Note: The DB structure I’m using here is MySQL, the structure may differ from other RDBS systems</p>
<p>From the above table schema, I made a composite key out of (user_id, order_id, status)</p>
<p>If we get the same payment request for the same order for a user, In MySQL, we would only have one row recorded, and for the subsequent insertion, MySQL throws a Duplicate entry error. In the application, we can either throw the error or explicitly capture the error from the DB engine and show a meaningful error message to the user.</p>
<p>I chose to go with this approach since it's more efficient than method 1 and 2 and it guarantees that there will not be duplicates and does not require to implementation of extra validation nor requires to use of any in-memory data stores.</p>
<p>Those who are resorting to NoSQL systems might need to look to the first three methods. Method 1 and 2 are not so efficient and queuing methods be the best option if your infrastructure supports them. I do not prefer to go with NoSQL databases as my primary data stores :-) but they are preferable choices as secondary storage.</p>
<p>Here is the <a target="_blank" href="https://github.com/manjufy/rest-api-concurrent-requests"><strong>GitHub Repo</strong></a> that demonstrates the Method 4 implementation written in NodeJs.</p>
<p>If there are other ways to deal with such operations, please feel to share, would love to get to know different ways of solving such operations.</p>
]]></content:encoded></item><item><title><![CDATA[Dockerize Phoenix + Tailwind application]]></title><description><![CDATA[Preamble
I'm hoping you already have a Phoenix application running If you happen to hop here directly.
If not, you may visit the previous two parts where we went through Creating a simple phoenix application, and how to set up Tailwind with phoenix.
...]]></description><link>https://manju.dev/dockerize-phoenix-tailwind-application</link><guid isPermaLink="true">https://manju.dev/dockerize-phoenix-tailwind-application</guid><category><![CDATA[Phoenix framework]]></category><category><![CDATA[elixir, tailwind]]></category><category><![CDATA[Elixir]]></category><category><![CDATA[Tailwind CSS]]></category><dc:creator><![CDATA[Manjunath Reddy]]></dc:creator><pubDate>Sat, 31 Dec 2022 00:30:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672446548493/2fa7f565-a85d-452e-b8f3-ff3cbffa4a1b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-preamble">Preamble</h2>
<p>I'm hoping you already have a Phoenix application running If you happen to hop here directly.</p>
<p>If not, you may visit the previous two parts where we went through Creating a <a target="_blank" href="https://manju.dev/creating-phoenix-application">simple phoenix application</a>, and <a target="_blank" href="https://manju.dev/setting-up-tailwind-css-in-a-phoenix-application">how to set up Tailwind with phoenix</a>.</p>
<h2 id="heading-build-a-production-ready-app-and-run-it-locally">Build a production-ready app and run it locally</h2>
<p>In this article, I'm directly going to jump into setting up the application to make it production ready by</p>
<ul>
<li><p>Testing it locally in production mode (<code>env=prod</code>)</p>
</li>
<li><p>Run the app in production mode to ensure our build process is correct,</p>
</li>
<li><p>And then use the same instructions to dockerize the application</p>
</li>
</ul>
<p>There is an excellent series written by <a class="user-mention" href="https://hashnode.com/@miguelcoba">Miguel Cobá</a> on how to prepare <a target="_blank" href="https://blog.miguelcoba.com/preparing-a-phoenix-16-app-for-deployment-with-elixir-releases">Phoenix application deployment with Elixir Releases</a> and also, official <a target="_blank" href="https://hexdocs.pm/phoenix/releases.html">elixir release</a> documentation.</p>
<p>However, the main reason I'm writing this article is that I bump into some obstacles to configure a Dockerfile to build the production-ready application. Here, I will go through the issues I have had in detail.</p>
<h3 id="heading-setup-compile-in-prod-mode">Setup, Compile In prod mode</h3>
<pre><code class="lang-plaintext">// Initial setup
$ mix deps.get --only prod
$ MIX_ENV=prod mix compile
</code></pre>
<p>With this, you may notice that there is a new folder created under <code>_build/</code> called <code>prod</code></p>
<pre><code class="lang-plaintext">// Compile assets
$ MIX_ENV=prod mix assets.deploy

// lets test the build with production mode
$ PORT=4000 MIX_ENV=prod SECRET_KEY_BASE=$(mix phx.gen.secret)  mix phx.server

// You will see the following instructions
╰─$ PORT=4000 MIX_ENV=prod SECRET_KEY_BASE=$(mix phx.gen.secret)  mix phx.server
00:21:54.461 [info] Running DockerPhoenixTailwindWeb.Endpoint with cowboy 2.9.0 at :::4000 (http)
00:21:54.468 [info] Access DockerPhoenixTailwindWeb.Endpoint at http://example.com
</code></pre>
<p>If you are wondering about the env variable <code>SECRET_KEY_BASE</code> which is a secret <a target="_blank" href="https://hexdocs.pm/phoenix/deployment.html#handling-of-your-application-secrets">Phoenix uses to sign and encrypt important information</a>.</p>
<p>Head over to http://localhost:4000 and ensure that the application is running.</p>
<h3 id="heading-generate-a-release-for-a-production-ready-build">Generate a release for a production-ready build</h3>
<p><mark>This is the step which </mark> <strong><mark>I missed during my entire process of preparing a release </mark></strong> and almost spent a day and a half to figure it out. Before generating a release, we need to uncomment the following in <code>/config/runtime.exs</code></p>
<pre><code class="lang-plaintext">config :docker_phoenix_tailwind, DockerPhoenixTailwindWeb.Endpoint, server: true
</code></pre>
<p>Essentially, it instructs Phoenix to start the webserver from the release.</p>
<p>Once the above line is uncommented, run the following command to generate the actual release</p>
<pre><code class="lang-plaintext">╰─$ MIX_ENV=prod mix release                                                                        
* assembling docker_phoenix_tailwind-0.1.0 on MIX_ENV=prod
* using config/runtime.exs to configure the release at runtime
* skipping elixir.bat for windows (bin/elixir.bat not found in the Elixir installation)
* skipping iex.bat for windows (bin/iex.bat not found in the Elixir installation)

Release created at _build/prod/rel/docker_phoenix_tailwind

    # To start your system
    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start

Once the release is running:

    # To connect to it remotely
    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind remote

    # To stop it gracefully (you may also send SIGINT/SIGTERM)
    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind stop

To list all commands:

    _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind
</code></pre>
<p>As shown above, the release is created at <code>_build/prod/rel/docker_phoenix_tailwind/bin/</code> folder,</p>
<p>Let's run it and see if the release works</p>
<pre><code class="lang-plaintext">╰─$ _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start
ERROR! Config provider Config.Reader failed with:
** (RuntimeError) environment variable SECRET_KEY_BASE is missing.
You can generate one by calling: mix phx.gen.secret

    /Users/manju/Codes/github/docker_phoenix_tailwind/_build/prod/rel/docker_phoenix_tailwind/releases/0.1.0/runtime.exs:17: (file)
    (elixir 1.14.2) src/elixir.erl:309: anonymous fn/4 in :elixir.eval_external_handler/1
    (stdlib 4.2) erl_eval.erl:748: :erl_eval.do_apply/7
    (stdlib 4.2) erl_eval.erl:492: :erl_eval.expr/6
    (stdlib 4.2) erl_eval.erl:136: :erl_eval.exprs/6
    (elixir 1.14.2) src/elixir.erl:294: :elixir.eval_forms/4
    (elixir 1.14.2) lib/module/parallel_checker.ex:107: Module.ParallelChecker.verify/1
    (elixir 1.14.2) lib/code.ex:425: Code.validated_eval_string/3
</code></pre>
<p>That did not work?</p>
<p>It's because we need to make sure to run the above command with a <code>SECRET_KEY_BASE</code> a secret which is required in production mode to encrypt important information. Let's try it again</p>
<pre><code class="lang-plaintext">╰─$ SECRET_KEY_BASE=$(mix phx.gen.secret) _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start
00:41:29.927 [info] Running DockerPhoenixTailwindWeb.Endpoint with cowboy 2.9.0 at :::4000 (http)
00:41:29.928 [info] Access DockerPhoenixTailwindWeb.Endpoint at http://example.com
</code></pre>
<p>Head over to the browser and test <a target="_blank" href="http://localhost:4000/">http://localhost:4000/</a>. The application now is running through a release executable (As we will do the same in production)</p>
<p>Let's sum up all the commands</p>
<pre><code class="lang-plaintext">$ mix deps.get --only prod
$ MIX_ENV=prod mix compile
$ MIX_ENV=prod mix assets.deploy
$ MIX_ENV=prod mix release
$ SECRET_KEY_BASE=$(mix phx.gen.secret) _build/prod/rel/docker_phoenix_tailwind/bin/docker_phoenix_tailwind start
</code></pre>
<p>It's been quite a journey to get the phoenix app release to make it work locally. In the following section, let's prepare a Dockerfile with all the instructions we went through so far.</p>
<h2 id="heading-lets-dockerize">Let's Dockerize</h2>
<p>Initially, I followed the Phoenix release documentation to generate the <a target="_blank" href="https://hexdocs.pm/phoenix/releases.html#containers">Dockerfile</a> automatically and try it build the image, and run the container, however, the container did not run due to incorrect instructions to build the assets and also missing <code>SECRET_KEY_BASE</code> and it took me a couple of hours to debug the Dockerfile.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672444731354/59e5fe91-97d8-4f14-a4f2-88daa08651d0.jpeg" alt class="image--center mx-auto" /></p>
<p>Then I stumbled upon two issues</p>
<ul>
<li><p>Assets weren't compiling (Tailwindcss) and therefore website was broken</p>
</li>
<li><p>Was running docker container without <code>SECRET_KEY_BASE</code></p>
</li>
</ul>
<p>To address the above issues, I slightly modified the <strong>Dockerfile</strong> instructions to build the assets correctly. This is instructed quite well in the Dockerfile comments, which I did not give much attention to.</p>
<p>We are not going to explore the entire Dockerfile instruction, I hope the comments are self-explanatory.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/manjufy/docker_phoenix_tailwind/blob/main/Dockerfile">https://github.com/manjufy/docker_phoenix_tailwind/blob/main/Dockerfile</a></div>
<p> </p>
<p>An important change to notice from the auto-generated Dockerfile is to ensure asset compilation <strong>is after copying lib</strong></p>
<h3 id="heading-setup-asset-compilation-correctly">Setup Asset compilation correctly</h3>
<p>In the Dockerfile, we need to make sure to setup asset compilation correctly so that our tailwind is working correctly.</p>
<p><strong>Before.</strong></p>
<p><mark>Notice that, in the following Docker instructions, we COPY the assets and then we immediately compile the assets, Which was causing assets not getting minified correctly.</mark></p>
<pre><code class="lang-plaintext"># Copy assets
# note: if your project uses a tool like https://purgecss.com/,
# which customizes asset compilation based on what it finds in
# your Elixir templates, you will need to move the asset compilation
# step down so that `lib` is available.
COPY assets assets
# Compile assets
RUN mix assets.deploy

# Compile project
COPY lib lib

RUN mix compile

# Copy runtime configuration file
COPY config/runtime.exs config/

# Assemble release
COPY rel rel
RUN mix release
</code></pre>
<p><strong>After</strong></p>
<p>Moved instruction <code>RUN mix assets.deploy</code> after, <code>COPY lib lib</code> instruction. Otherwise, assets (JS and CSS) won't be properly minified.</p>
<pre><code class="lang-plaintext"># Copy assets
# note: if your project uses a tool like https://purgecss.com/,
# which customizes asset compilation based on what it finds in
# your Elixir templates, you will need to move the asset compilation
# step down so that `lib` is available.
COPY assets assets

# Compile project
COPY lib lib

# IMPORTANT: Make sure asset compilation is after copying lib
# Compile assets
RUN mix assets.deploy

RUN mix compile

# Copy runtime configuration file
COPY config/runtime.exs config/

# Assemble release
COPY rel rel
RUN mix release
</code></pre>
<h3 id="heading-build-the-image-and-run-it-from-a-container">Build the image and run it from a container</h3>
<p>Copy and create the <a target="_blank" href="https://github.com/manjufy/docker_phoenix_tailwind/blob/main/Dockerfile">Dockerfle</a> in the project root directory.</p>
<p>Build the image</p>
<pre><code class="lang-plaintext">╰─$ docker image build -t elixir/docker_phoenix_tailwind .
</code></pre>
<p>Run the container</p>
<pre><code class="lang-plaintext">╰─$ docker run -e SECRET_KEY_BASE="$(mix phx.gen.secret)" -p  4000:4000 elixir/docker_phoenix_tailwind
00:17:50.396 [info] Running DockerPhoenixTailwindWeb.Endpoint with cowboy 2.9.0 at :::4000 (http)
00:17:50.396 [info] Access DockerPhoenixTailwindWeb.Endpoint at http://example.com
</code></pre>
<p>Now head over to http://localhost:4000. We now have an application running from the container which you could use to deploy in production.</p>
<p>The main Dockerfile is based on <strong>Debian bullseye</strong>, however, I have also experimented with an <strong>alpine image</strong> which I have explained in the <a target="_blank" href="https://github.com/manjufy/docker_phoenix_tailwind/blob/main/README.md#dockerize-with-alpine-dockerfilealpine">README</a>.</p>
<p>The entire application source is here</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/manjufy/docker_phoenix_tailwind">https://github.com/manjufy/docker_phoenix_tailwind</a></div>
<p> </p>
<p>For now, that completes the series. However, in the future, I would like to explore deploying by containerising the application on different platforms such as Digitalocean, and Fly.io.</p>
]]></content:encoded></item><item><title><![CDATA[Setting up Tailwind CSS In a Phoenix application]]></title><description><![CDATA[In the previous article, we explored setting up a basic Phoenix application. If you haven't read the first part yet, Just take a moment to read it before this post :-).
However, if you already have an existing phoenix application running, and if you ...]]></description><link>https://manju.dev/setting-up-tailwind-css-in-a-phoenix-application</link><guid isPermaLink="true">https://manju.dev/setting-up-tailwind-css-in-a-phoenix-application</guid><category><![CDATA[Phoenix framework]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[elixir, tailwind]]></category><category><![CDATA[Elixir]]></category><dc:creator><![CDATA[Manjunath Reddy]]></dc:creator><pubDate>Fri, 30 Dec 2022 22:42:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672440264522/a28beb02-a88d-4bcc-b887-74b0e6a5de64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous article, we explored setting up a basic Phoenix application. If you haven't read the first part yet, <a target="_blank" href="https://manju.dev/creating-phoenix-application">Just take a moment to read it</a> before this post :-).</p>
<p>However, if you already have an existing phoenix application running, and if you would like to configure Tailwind with it, skip the first part.</p>
<h3 id="heading-preamble">Preamble</h3>
<h3 id="heading-install-andamp-configure-tailwind-plugin">Install &amp; Configure Tailwind Plugin</h3>
<p>Let me be DRY, and not going to repeat what's been documented quite nicely by the <a target="_blank" href="https://tailwindcss.com/docs/guides/phoenix">Tailwind official website on how to set up Tailwind CSS in a Phoenix project.</a> Steps are elegantly explained.</p>
<iframe src="https://giphy.com/embed/l0ExbnGIX9sMFS7PG" class="giphy-embed" width="480" height="264"></iframe>

<p><a target="_blank" href="https://giphy.com/gifs/l0ExbnGIX9sMFS7PG">via GIPHY</a></p>
<p>Re-run the application. At this point, It looks like</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672437207837/78b72c30-6860-4c9b-a506-6bb917d1965c.png" alt class="image--center mx-auto" /></p>
<p>I know it looks ugly :-). That completes the first part of this article. In the following section, let's beautify the application to make it look nice.</p>
<h3 id="heading-theme-your-application">Theme your application</h3>
<p>Let's create a simple page which uses tailwind.</p>
<p>Look for <code>lib/docker_phoenix_tailwind_web/templates/layout/root.html.heex</code> file and replace it with</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">csrf_meta_tag</span>() %&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> <span class="hljs-attr">live_title_tag</span> <span class="hljs-attr">assigns</span>[<span class="hljs-attr">:page_title</span>] || "", <span class="hljs-attr">suffix:</span> " · <span class="hljs-attr">Micheal</span> <span class="hljs-attr">Schumacher</span>" %&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">phx-track-static</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{Routes.static_path(@conn,</span> "/<span class="hljs-attr">assets</span>/<span class="hljs-attr">app.css</span>")}/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">defer</span> <span class="hljs-attr">phx-track-static</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{Routes.static_path(@conn,</span> "/<span class="hljs-attr">assets</span>/<span class="hljs-attr">app.js</span>")}&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"font-sans antialiased leading-normal tracking-wider text-gray-900 bg-cover"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">%=</span> @<span class="hljs-attr">inner_content</span> %&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>And look for our content page which is <code>lib/docker_phoenix_tailwind_web/templates/page/index.html.heex</code> and replace it with, which contains a simple layout</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"min-h-screen flex flex-col"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-2 bg-indigo-200"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-2xl font-bold text-center"</span>&gt;</span>Phoenix Framework<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex-1 bg-indigo-50 p-2"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-20"</span>&gt;</span>Content goes here....<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">footer</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-2 bg-indigo-200"</span>&gt;</span>Footer section<span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>This is how my <code>assets/css/app.css</code> looks like</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;

<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background-color</span>: crimson;
}
</code></pre>
<p>And my theme <code>assets/tailwind.config.js</code></p>
<pre><code class="lang-css">// <span class="hljs-selector-tag">See</span> <span class="hljs-selector-tag">the</span> <span class="hljs-selector-tag">Tailwind</span> <span class="hljs-selector-tag">configuration</span> <span class="hljs-selector-tag">guide</span> <span class="hljs-selector-tag">for</span> <span class="hljs-selector-tag">advanced</span> <span class="hljs-selector-tag">usage</span>
// <span class="hljs-selector-tag">https</span>://<span class="hljs-selector-tag">tailwindcss</span><span class="hljs-selector-class">.com</span>/<span class="hljs-selector-tag">docs</span>/<span class="hljs-selector-tag">configuration</span>

<span class="hljs-selector-tag">let</span> <span class="hljs-selector-tag">plugin</span> = <span class="hljs-selector-tag">require</span>('<span class="hljs-selector-tag">tailwindcss</span>/<span class="hljs-selector-tag">plugin</span>')

<span class="hljs-selector-tag">module</span><span class="hljs-selector-class">.exports</span> = {
  <span class="hljs-attribute">content</span>: [
    <span class="hljs-string">'./js/**/*.js'</span>,
    <span class="hljs-string">'../lib/*_web.ex'</span>,
    <span class="hljs-string">'../lib/*_web/**/*.*ex'</span>
  ],
  mode: <span class="hljs-string">'jit'</span>,
  theme: {
    extend: {},
  },
  <span class="hljs-selector-tag">plugins</span>: <span class="hljs-selector-attr">[    require(<span class="hljs-string">'@tailwindcss/forms'</span>),    plugin(({addVariant}) =&gt; addVariant(<span class="hljs-string">'phx-no-feedback'</span>, [<span class="hljs-string">'&amp;.phx-no-feedback'</span>, <span class="hljs-string">'.phx-no-feedback &amp;'</span>]</span>)),
    <span class="hljs-selector-tag">plugin</span>(({addVariant}) =&gt; <span class="hljs-selector-tag">addVariant</span>('<span class="hljs-selector-tag">phx-click-loading</span>', <span class="hljs-selector-attr">[<span class="hljs-string">'&amp;.phx-click-loading'</span>, <span class="hljs-string">'.phx-click-loading &amp;'</span>]</span>)),
    <span class="hljs-selector-tag">plugin</span>(({addVariant}) =&gt; <span class="hljs-selector-tag">addVariant</span>('<span class="hljs-selector-tag">phx-submit-loading</span>', <span class="hljs-selector-attr">[<span class="hljs-string">'&amp;.phx-submit-loading'</span>, <span class="hljs-string">'.phx-submit-loading &amp;'</span>]</span>)),
    <span class="hljs-selector-tag">plugin</span>(({addVariant}) =&gt; <span class="hljs-selector-tag">addVariant</span>('<span class="hljs-selector-tag">phx-change-loading</span>', <span class="hljs-selector-attr">[<span class="hljs-string">'&amp;.phx-change-loading'</span>, <span class="hljs-string">'.phx-change-loading &amp;'</span>]</span>))
  ]
}
</code></pre>
<p>With that, Re-run the application and you will have a simple tailwind themed page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672439916304/982399f9-24ab-4869-8ebb-3f722802204e.png" alt class="image--center mx-auto" /></p>
<p>Please leave a comment if you happen to face any issues configuring Tailwind.</p>
<p>In the next section, we will explore running the <a target="_blank" href="https://manju.dev/dockerize-phoenix-tailwind-application"><strong>Phoenix application locally with a production release build</strong></a>, so that we could use the same instructions to build our Dockerfile.</p>
]]></content:encoded></item><item><title><![CDATA[Creating Phoenix Application]]></title><description><![CDATA[I have been wanting to explore and create a simple blog with Phoenix and Tailwind for quite a long. However, I struggle a bit to get it up and running and also to be able to dockerise to make it ready for production deployment.
Preamble
This is the f...]]></description><link>https://manju.dev/creating-phoenix-application</link><guid isPermaLink="true">https://manju.dev/creating-phoenix-application</guid><category><![CDATA[elixir, tailwind]]></category><category><![CDATA[Phoenix framework]]></category><dc:creator><![CDATA[Manjunath Reddy]]></dc:creator><pubDate>Fri, 30 Dec 2022 20:22:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672434150326/a847cfc7-6ce8-4304-ae7c-f4144deb0b0e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been wanting to explore and create a simple blog with Phoenix and Tailwind for quite a long. However, I struggle a bit to get it up and running and also to be able to dockerise to make it ready for production deployment.</p>
<h3 id="heading-preamble">Preamble</h3>
<p>This is the first part of the series, where I explore, creating a phoenix application, configuring tailwind, dockerizing the application and exploring creating a release, that we can run in production. Note that, we <strong>omitted using a Database</strong> in this series. I would explore that in a separate section. The goal for this series is to be able to prepare, dockerize, and deploy the phoenix application to production quickly.</p>
<h3 id="heading-what-well-need">What We'll need?</h3>
<p>My development machine is macOS, and most of my instructions will be catered for mac, however, instructions could be closer to Linux machines as well.</p>
<p><strong>First,</strong> we need to have <strong>elixir installed</strong>. Please follow the installation instructions from the official elixir website <a target="_blank" href="https://elixir-lang.org/install.html">https://elixir-lang.org/install.html</a></p>
<p>Once you have elixir installed, Erlang will be automatically installed.</p>
<p>Let's check if we have elixir installed. Note that, Erlang will be automatically installed with Elixir.</p>
<pre><code class="lang-plaintext">╰─$ elixir -v                                                                                                                                                                                                     
Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]

Elixir 1.14.2 (compiled with Erlang/OTP 25)
</code></pre>
<p><strong>Next</strong>, we need to install Phoenix application generated to be able to create initial Phoenix application.</p>
<pre><code class="lang-plaintext">╰─$ mix archive.install hex phx_new

// Check if its installed
╰─$ mix phx.new --version                                                                                                                                                                          
Phoenix installer v1.6.2
</code></pre>
<p>The above command would install phoenix generator. Just for info, it will be located under <code>/Users/{username}/.mix/archives/phx_new-{version}</code></p>
<p>In conclusion, we need to have `Erlang &gt; Elixir &gt; Phoenix generator` to be able to create a new phoenix app.</p>
<h3 id="heading-initialising-the-application">Initialising the application</h3>
<p>As I explained earlier, we create the phoenix application without a database. Use the following command to generate a basic phoneix application.</p>
<pre><code class="lang-plaintext">╰─$ mix phx.new --no-ecto docker_phoenix_tailwind
......
* running mix deps.get
[warning] :rebar is no longer supported in Mix, falling back to :rebar3

We are almost there! The following steps are missing:

    $ cd docker_phoenix_tailwind

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server
// Which will create the project and also installs dependencies

// Then
╰─$ cd docker_phoenix_tailwind
╰─$ docker_phoenix_tailwind &gt; mix phx.server
</code></pre>
<p>At the time of this writing, I get the following warning to remove the atom <code>:gettext</code> entry from <code>mix.exs</code> file</p>
<p><code>warning: the :gettext compiler is no longer required in your mix.exs.</code></p>
<p>Go ahead and remove this entry.</p>
<p>If you are wondering what is Gettext, it's a module to work with <a target="_blank" href="https://hexdocs.pm/gettext/Gettext.html">internationalised applications</a></p>
<pre><code class="lang-plaintext">mix.exs =&gt; projection section would look like
// Before
  def project do
    [
     ....
      compilers: [:gettext] ++ Mix.compilers(),
     ....
    ]
  end

// After  
def project do
    [
     ....
      compilers: [] ++ Mix.compilers(),
     ....
    ]
  end
</code></pre>
<blockquote>
<p>Note: Do not remove the dependency gettext from deps secton. Only remove it from project compilers list.</p>
</blockquote>
<p>Stop the application (`ctrc-c` twice), and Re-run the application again</p>
<pre><code class="lang-plaintext">╰─$ mix phx.server                                                                                  
Generated docker_phoenix_tailwind app
[info] Running DockerPhoenixTailwindWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[info] Access DockerPhoenixTailwindWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...
</code></pre>
<p>By default, applications run at port 4000, type http://localhost:4000 in your browser, and you will be greeted with the Phoenix Framework home page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1672431345928/b49f6744-b71d-4b02-8cf4-b8c53db7b866.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-monitoring-and-debugging">Monitoring and Debugging</h3>
<p>Another cool thing about the Phoenix framework is that it comes with a default <a target="_blank" href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html">LiveDhasboard</a> that provides real-time monitoring and debugging tools for the application. Head over to <a target="_blank" href="http://localhost:4000/dashboard/home">http://localhost:4000/dashboard</a> to view the LiveDashboard</p>
<p><a target="_blank" href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html"><img src="https://github.com/phoenixframework/phoenix_live_dashboard/raw/master/screenshot.png" alt="Source: https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html" /></a></p>
<p>Source: <a target="_blank" href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html">https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.html</a></p>
<p>That's it in this episode. In the next article, we explore configuring <a target="_blank" href="https://tailwindcss.com/">Tailwind</a> with our Phoenix application.</p>
]]></content:encoded></item></channel></rss>