<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://at15.dev/blog</id>
    <title>at15 blog Blog</title>
    <updated>2026-01-23T06:44:55.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://at15.dev/blog"/>
    <subtitle>at15 blog Blog</subtitle>
    <icon>https://at15.dev/img/at15.png</icon>
    <entry>
        <title type="html"><![CDATA[Going back to Go: Concurrency Patterns]]></title>
        <id>https://at15.dev/blog/2024/12/going-back-to-go-concurrency-patterns</id>
        <link href="https://at15.dev/blog/2024/12/going-back-to-go-concurrency-patterns"/>
        <updated>2026-01-23T06:44:55.000Z</updated>
        <summary type="html"><![CDATA[Building blocks for concurrency in go (goroutine, channel) and patterns (fan out).]]></summary>
        <content type="html"><![CDATA[<p>Building blocks for concurrency in go (goroutine, channel) and patterns (fan out).</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="todo">TODO<a href="https://at15.dev/blog/2024/12/going-back-to-go-concurrency-patterns#todo" class="hash-link" aria-label="Direct link to TODO" title="Direct link to TODO">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->concurrency is not parallelism by rob pike <a href="https://go.dev/blog/waza-talk" target="_blank" rel="noopener noreferrer">https://go.dev/blog/waza-talk</a></li>
</ul>
<p>Basic</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->start (and stop) go routine</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->channel, buffer, not buffered, select</li>
</ul>
<p>Patterns (I can think of top of my head)</p>
<ul>
<li>pipeline<!-- -->
<ul>
<li>fan out (one web page has multiple links)</li>
<li>fan in, wait until multiple part download has finished</li>
</ul>
</li>
<li>limit parallelism, e.g. at most two concurrent outgoing requests</li>
</ul>
<p>Libraries</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->error group</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->single flight</li>
</ul>
<p>Top google search results</p>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <a href="https://go.dev/blog/pipelines" target="_blank" rel="noopener noreferrer">https://go.dev/blog/pipelines</a> Go Concurrency Patterns: Pipelines and cancellation</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <a href="https://github.com/lotusirous/go-concurrency-patterns" target="_blank" rel="noopener noreferrer">https://github.com/lotusirous/go-concurrency-patterns</a></li>
<li class="task-list-item"><input type="checkbox" disabled=""> <a href="https://www.oreilly.com/library/view/concurrency-in-go/9781491941294/ch04.html" target="_blank" rel="noopener noreferrer">https://www.oreilly.com/library/view/concurrency-in-go/9781491941294/ch04.html</a> book from oreilly</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <a href="https://reliasoftware.com/blog/golang-concurrency-patterns" target="_blank" rel="noopener noreferrer">https://reliasoftware.com/blog/golang-concurrency-patterns</a> generator pattern?</li>
</ul>]]></content>
        <category label="go" term="go"/>
        <category label="concurrency" term="concurrency"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Run Docker Registry at Home with HTTPS]]></title>
        <id>https://at15.dev/blog/2026/01/run-docker-registry-at-home-with-https</id>
        <link href="https://at15.dev/blog/2026/01/run-docker-registry-at-home-with-https"/>
        <updated>2026-01-22T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[Run Docker Registry at home with HTTPS using letsencrypt.]]></summary>
        <content type="html"><![CDATA[<p>Run Docker Registry at home with HTTPS using letsencrypt.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="background">Background<a href="https://at15.dev/blog/2026/01/run-docker-registry-at-home-with-https#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background">​</a></h2>
<p>Just like everyone (on twitter), I spent a lot of time vibe coding personal tools
such as bookmark manager, postgres UI etc. I want to deploy these applications
on my home server as containers. I don't want to push these containers to
public/private registry because it is faster and cheaper to use a local registry.</p>
<p>You can run docker registry as a docker container easily:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:#36acaa">-d</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--name</span><span class="token plain"> registry </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5000</span><span class="token plain">:5000 registry:3</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>However, you can only access it via http unless you give it a
<a href="https://distribution.github.io/distribution/about/deploying/#get-a-certificate" target="_blank" rel="noopener noreferrer">cert for https</a>,
or configure docker daemon and other places to trust the insecure registry.</p>
<p>One easy way to get a cert is using tools like <a href="https://github.com/FiloSottile/mkcert" target="_blank" rel="noopener noreferrer">mkcert</a>
to install a local CA and issue cert for e.g. <code>docker.registry.lan</code>. However,
this does not work when you run registry on one machine (e.g. ubuntu) and want
to access from other machines (e.g. mac). It also does not work when you access
from a container unless you install the CA in the container.</p>
<p>ChatGPT suggested using <a href="https://smallstep.com/docs/step-ca/" target="_blank" rel="noopener noreferrer">step-ca</a>, after
looking at the instructions, I decided to just google homelab https and found a
<a href="https://www.reddit.com/r/homelab/comments/1l7a9bn/how_to_do_https_on_local_domains_in_a_safe_way/" target="_blank" rel="noopener noreferrer">reddit post</a>.
I know Let's Encrypt, but I always thought you need to run a live server to pass
the verification to get a cert. Turns out you can also update DNS record and get
the cert without running a server for the (wildcard) domain. There are tons of
tools out there, certbot, acme.sh. I decided to use <a href="https://github.com/go-acme/lego" target="_blank" rel="noopener noreferrer">lego</a>
because it is written in Go.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-cert-using-lego-with-domain-managed-by-cloudflare">Get cert using lego with domain managed by Cloudflare<a href="https://at15.dev/blog/2026/01/run-docker-registry-at-home-with-https#get-cert-using-lego-with-domain-managed-by-cloudflare" class="hash-link" aria-label="Direct link to Get cert using lego with domain managed by Cloudflare" title="Direct link to Get cert using lego with domain managed by Cloudflare">​</a></h2>
<p>First get a Cloudflare token, go to <a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank" rel="noopener noreferrer">https://dash.cloudflare.com/profile/api-tokens</a>
and use the <code>Edit Zone DNS</code> template:</p>
<ul>
<li>Zone, DNS, Edit</li>
<li>Include, Specific zone, <code>&lt;your-domain&gt;</code>, e.g. <code>example.com</code></li>
</ul>
<p>Install lego:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">go </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> github.com/go-acme/lego/v4/cmd/lego@latest</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Set the token via env (<a href="https://go-acme.github.io/lego/dns/cloudflare/" target="_blank" rel="noopener noreferrer">doc</a>):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token assign-left variable" style="color:#36acaa">CLOUDFLARE_DNS_API_TOKEN</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">1234567890abcdefghijklmnopqrstuvwxyz </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">lego </span><span class="token parameter variable" style="color:#36acaa">--dns</span><span class="token plain"> cloudflare </span><span class="token parameter variable" style="color:#36acaa">-d</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'*.example.com'</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-d</span><span class="token plain"> example.com run</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then you get files in <code>~/.lego/certificates/&lt;your-domain&gt;.crt|key</code>. You can pass
the crt and key file to docker registry container directly:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># https://distribution.github.io/distribution/about/deploying/#get-a-certificate</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:#36acaa">-d</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">--restart</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">always </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">--name</span><span class="token plain"> registry </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-v</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"</span><span class="token string variable" style="color:#36acaa">$(</span><span class="token string variable builtin class-name" style="color:#36acaa">pwd</span><span class="token string variable" style="color:#36acaa">)</span><span class="token string" style="color:#e3116c">"</span><span class="token plain">/certs:/certs </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-e</span><span class="token plain"> </span><span class="token assign-left variable" style="color:#36acaa">REGISTRY_HTTP_ADDR</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0.0</span><span class="token plain">.0.0:443 </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-e</span><span class="token plain"> </span><span class="token assign-left variable" style="color:#36acaa">REGISTRY_HTTP_TLS_CERTIFICATE</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">/certs/domain.crt </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-e</span><span class="token plain"> </span><span class="token assign-left variable" style="color:#36acaa">REGISTRY_HTTP_TLS_KEY</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">/certs/domain.key </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-p</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">443</span><span class="token plain">:443 </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  registry:3</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>I have a proxy running on my home server, I just let the proxy run with https
and forward requests to registry container running plain http. The cert is for
wildcard domain <code>*.example.com</code> so I can route based on host in the proxy,
e.g. <code>docker.example.com</code> proxies to <code>localhost:5000</code>. This also allows all my
other applications to get https, e.g. <code>pgui.example.com</code>.</p>
<p>Using a proxy server like <a href="https://github.com/caddyserver/caddy" target="_blank" rel="noopener noreferrer">caddy</a> could
simplify the setup (caddy has builtin https issuance and renewal). But I decided
to write (vibe) my own proxy server in Go.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="configure-router-to-resolve-domain">Configure router to resolve domain<a href="https://at15.dev/blog/2026/01/run-docker-registry-at-home-with-https#configure-router-to-resolve-domain" class="hash-link" aria-label="Direct link to Configure router to resolve domain" title="Direct link to Configure router to resolve domain">​</a></h2>
<p>I got the cert for domain <code>*.example.com</code>. However, if I do not do anything in
my router, the DNS resolve will lead me to nothing because I don't even have a
DNS record for that domain. The DNS verification method only checks TXT record
for domain ownership. My router <a href="https://www.gl-inet.com/products/gl-be9300/" target="_blank" rel="noopener noreferrer">flint3</a>
comes with OpenWRT and LuCI so I can update DNS to point the domain to local IP:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">/example.com/192.168.1.123</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="testing">Testing<a href="https://at15.dev/blog/2026/01/run-docker-registry-at-home-with-https#testing" class="hash-link" aria-label="Direct link to Testing" title="Direct link to Testing">​</a></h2>
<p>Tag and push an image to the registry:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> pull alpine:latest</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> tag alpine:latest docker.example.com/alpine:latest</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> push docker.example.com/alpine:latest</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Pull the image from another machine on the same network:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> pull docker.example.com/alpine:latest</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="docker" term="docker"/>
        <category label="registry" term="registry"/>
        <category label="https" term="https"/>
        <category label="vibe" term="vibe"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Union error type in Go]]></title>
        <id>https://at15.dev/blog/2025/03/union-error-type-in-go</id>
        <link href="https://at15.dev/blog/2025/03/union-error-type-in-go"/>
        <updated>2025-03-03T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[Force returning a subset of error types from a function in Go.]]></summary>
        <content type="html"><![CDATA[<p>Force returning a subset of error types from a function in Go.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="background">Background<a href="https://at15.dev/blog/2025/03/union-error-type-in-go#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background">​</a></h2>
<p>Go has a very simple error handling mechanism. A function in Go can return multiple values,
the last value can be an <code>error</code> type. The <code>error</code> interface is simple:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The easiest way to create a new error is using <code>fmt.Errorf</code> or <code>errors.New</code>.
However, error created from string is pretty freeform and caller cannot extract
structured information out of it reliably.
A more structured way is to define a struct that implements the <code>error</code> interface.
For example, we can define a <code>UserError</code> and <code>ValidationError</code>:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> UserError </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    UserName </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Code </span><span class="token builtin">int</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// assume this service has some registry of error code</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Message </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">UserError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"user: %s, code: %d, message: %s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">UserName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Code</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> ValidationError </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Message </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Field </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">e </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">ValidationError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Sprintf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"validation error on field: %s, message: %s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Field</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>To return one of the error types, we just write <code>error</code> in the function signature, e.g.</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">doSomething</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">v </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> v </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">ValidationError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Message</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"value is empty"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Field</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> v </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">UserError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            UserName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Code</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Message</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"user is not allowed"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Now there are two issues we need to address:</p>
<ul>
<li>Let the caller know what type of error will be returned without looking at the implementation.</li>
<li>Force the implementation to return one of the error types and not using <code>fmt.Errorf</code> lazily.</li>
</ul>
<p>In Rust, you can use <code>Result&lt;T, E&gt;</code> where <code>E</code> is a union of error types, e.g.</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">enum</span><span class="token plain"> </span><span class="token type-definition class-name">MyError</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token class-name">UserError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">UserError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token class-name">ValidationError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">ValidationError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>In Go, there is no union (or pattern matching...), the closest is interface and type assertion.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="define-a-interface-as-union-for-error-types">Define a interface as union for error types<a href="https://at15.dev/blog/2025/03/union-error-type-in-go#define-a-interface-as-union-for-error-types" class="hash-link" aria-label="Direct link to Define a interface as union for error types" title="Direct link to Define a interface as union for error types">​</a></h2>
<p>We can define a interface that has a speical marker. e.g. <code>APIError</code></p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> APIError </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token builtin">error</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">IsAPIError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">UserError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">IsAPIError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">u </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">ValidationError</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">IsAPIError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Now we can use <code>APIError</code> in the function signature, e.g.</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">doSomething</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">v </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> APIError </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> v </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">ValidationError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Message</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"value is empty"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Field</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> v </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">UserError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            UserName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Code</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">     </span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            Message</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">  </span><span class="token string" style="color:#e3116c">"user is not allowed"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>To inspect the error type after wrapping using <code>fmt.Errorf</code>, we can use <code>errors.As</code>.</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">wrapError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">v </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">doSomething</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">v</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Errorf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"wrapped: %w"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token plain"> APIError </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">UserError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">_</span><span class="token plain"> APIError </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">ValidationError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">TestAPIError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">t </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">testing</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">T</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">wrapError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"admin"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> u </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">UserError</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> errors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">As</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">u</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Logf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"user error: %s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> u</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">wrapError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> v </span><span class="token operator" style="color:#393A34">*</span><span class="token plain">ValidationError</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> errors</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">As</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">v</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		t</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Logf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"validation error: %s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> v</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This is not that straightfoward in the beginning, but makes the code easier to maintain
in the long run. Especially if you are writing a library. The type system is more up to
date than seprated documentation. Further more, for writing API server/client, these
error types can be defined in schema and generated directly.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-not-using-generic">Why not using generic<a href="https://at15.dev/blog/2025/03/union-error-type-in-go#why-not-using-generic" class="hash-link" aria-label="Direct link to Why not using generic" title="Direct link to Why not using generic">​</a></h2>
<p>Go's generic does allow you to define an interface that looks like a union, e.g.</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> MyError </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    UserError </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> ValidationError</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>However, you cannot return different types from a generic function, e.g.</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> doSomething</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">T MyError</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">v </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> T </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> v </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">ValidationError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">			Message</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"value is empty"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">			Field</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">   </span><span class="token string" style="color:#e3116c">"v"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> v </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token plain">UserError</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">			UserName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"admin"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">			Code</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">     </span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">			Message</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">  </span><span class="token string" style="color:#e3116c">"user is not allowed"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Will lead to error:</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cannot use &amp;ValidationError{…} (value of type *ValidationError) as T value in return statement: cannot assign *ValidationError to UserError (in T)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The polymorphism is compile time, not runtime like Java's interface.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="references">References<a href="https://at15.dev/blog/2025/03/union-error-type-in-go#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References">​</a></h2>
<p>Stacktrace (not related to this post, but came across them ...)</p>
<ul>
<li><a href="https://github.com/golang/go/issues/60873" target="_blank" rel="noopener noreferrer">proposal: errors: add (stack)trace at error annotation</a></li>
<li><a href="https://gitlab.com/tozd/go/errors" target="_blank" rel="noopener noreferrer">tozd/go/errors</a></li>
<li><a href="https://github.com/go-errors/errors" target="_blank" rel="noopener noreferrer">go-errors/errors</a></li>
<li><a href="https://github.com/dyweb/gommon/tree/master/errors" target="_blank" rel="noopener noreferrer">dyweb/gommon/errors</a></li>
</ul>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="go" term="go"/>
        <category label="error" term="error"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Download object from S3 in parallel using presigned url]]></title>
        <id>https://at15.dev/blog/2025/02/download-object-from-s3-in-parallel</id>
        <link href="https://at15.dev/blog/2025/02/download-object-from-s3-in-parallel"/>
        <updated>2025-02-10T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to download object from S3 in parallel using a presigned url.]]></summary>
        <content type="html"><![CDATA[<p>How to download object from S3 in parallel using a presigned url.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="background">Background<a href="https://at15.dev/blog/2025/02/download-object-from-s3-in-parallel#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background">​</a></h2>
<p>S3 supports downloading large object in parts i.e. subset of the file by specifying the <a href="https://docs.aws.amazon.com/whitepapers/latest/s3-optimizing-performance-best-practices/use-byte-range-fetches.html" target="_blank" rel="noopener noreferrer">byte range</a>.
AWS cli and SDK provides builtin support e.g. TransferManager in Java, s3manager in Go.
However, using AWS SDK/cli requires providing AWS credentials.
For sharing objects in a private bucket, it's a common practice to use <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html" target="_blank" rel="noopener noreferrer">presigned url</a> to provide temporary access for downloading or uploading files.</p>
<p>The question is can we use range download with presigned url? The answer is yes.
You don't need AWS SDK to set the byte range, you only need AWS SDK to create the
presigned url.
For one signed url, you can have multiple parallel requests with different byte range.
Following steps show how to download a presigned url in parallel using curl.
Which also apply to programitc download in Go, Python, etc.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="steps">Steps<a href="https://at15.dev/blog/2025/02/download-object-from-s3-in-parallel#steps" class="hash-link" aria-label="Direct link to Steps" title="Direct link to Steps">​</a></h2>
<p>First, generate a presigned url for an existing object in existing bucket. It would attach
<code>?X-Amz-Algorithm ....</code> to the s3 url you provided.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">aws s3 presign s3://find-a-unique-bucket-name-is-hard/my-large-file.mp4 --expires-in </span><span class="token number" style="color:#36acaa">604800</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Get bytes of the object so we can define the byte range base on total size.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">aws s3api head-object </span><span class="token parameter variable" style="color:#36acaa">--bucket</span><span class="token plain"> find-a-unique-bucket-name-is-hard </span><span class="token parameter variable" style="color:#36acaa">--key</span><span class="token plain"> my-large-file.mp4 </span><span class="token parameter variable" style="color:#36acaa">--query</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ContentLength'</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--output</span><span class="token plain"> text</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Then download part of the object by specifying the byte range http header using curl.
Assume the object size is 100 bytes, the range is inclusive for both start and end,
two parallel requests use range 0-49 and 50-100.
To download in parallel, open two terminals and run two commands about the same time...</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-H</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Range: bytes=0-49"</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://s3.us-west-1.amazonaws.com/find-a-unique-bucket-name-is-hard/my-large-file.mp4?X-Amz..."</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> p1.part</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-H</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Range: bytes=50-100"</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://s3.us-west-1.amazonaws.com/find-a-unique-bucket-name-is-hard/my-large-file.mp4?X-Amz..."</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> p2.part</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Finnaly merge the parts to a new file (<code>cat</code> is pretty fast...).</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">cat</span><span class="token plain"> p1.part p2.part </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> my-large-file.mp4</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Now you can play the merged video file to check if the file is correct.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="references">References<a href="https://at15.dev/blog/2025/02/download-object-from-s3-in-parallel#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References">​</a></h2>
<ul>
<li><a href="https://docs.aws.amazon.com/whitepapers/latest/s3-optimizing-performance-best-practices/use-byte-range-fetches.html" target="_blank" rel="noopener noreferrer">AWS - Use byte-range fetches</a></li>
</ul>
<p>Go</p>
<ul>
<li><a href="https://github.com/aws/aws-sdk-go/blob/main/service/s3/s3manager/download.go" target="_blank" rel="noopener noreferrer">aws-sdk-go s3manager</a> and <a href="https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3#GetObjectInput" target="_blank" rel="noopener noreferrer">s3.GetObjectInput</a></li>
</ul>
<p>Java</p>
<ul>
<li><a href="https://aws.amazon.com/blogs/developer/parallelizing-large-downloads-for-optimal-speed/" target="_blank" rel="noopener noreferrer">AWS - Parallelizing large downloads for optimal speed</a> the TransferManager in AWS SDK for Java.</li>
</ul>
<p>Python</p>
<ul>
<li><a href="https://news.ycombinator.com/item?id=26764067" target="_blank" rel="noopener noreferrer">https://news.ycombinator.com/item?id=26764067</a>
<ul>
<li><a href="https://github.com/Netflix/metaflow/blob/32bfbdff5880720b9bbba5a9e8df8d413f31505e/metaflow/plugins/datatools/s3/s3.py#L905" target="_blank" rel="noopener noreferrer">Netflix megaflow datatools</a> range in python</li>
</ul>
</li>
<li><a href="https://github.com/GoogleCloudPlatform/gsutil/blob/d9d7b0f3f8936e285bc6ff60de04062b808ee3f8/gslib/utils/copy_helper.py#L2639" target="_blank" rel="noopener noreferrer">gsutil</a></li>
<li><a href="https://github.com/bloomreach/s4cmd" target="_blank" rel="noopener noreferrer">s4cmd</a></li>
<li><a href="https://github.com/rxvt/s3fetch" target="_blank" rel="noopener noreferrer">s3fetch</a></li>
</ul>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="aws" term="aws"/>
        <category label="s3" term="s3"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Going back to Go: A series]]></title>
        <id>https://at15.dev/blog/2024/12/going-back-to-go</id>
        <link href="https://at15.dev/blog/2024/12/going-back-to-go"/>
        <updated>2024-12-12T12:00:00.000Z</updated>
        <summary type="html"><![CDATA[Revisit basic of Go (Golang) and new features with runnable examples.]]></summary>
        <content type="html"><![CDATA[<p>Revisit basic of Go (Golang) and new features with runnable examples.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="background">Background<a href="https://at15.dev/blog/2024/12/going-back-to-go#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background">​</a></h2>
<p>I haven't used Go extensively for a while, and I am not keeping up with recent releases of Go.
I think it is a good time to recap frequently used features and explore new features and write
some simple projects to practice them.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="plan">Plan<a href="https://at15.dev/blog/2024/12/going-back-to-go#plan" class="hash-link" aria-label="Direct link to Plan" title="Direct link to Plan">​</a></h2>
<p>NOTE: Subject to change, link to blogs will be updated when they are published (if ever).</p>
<p>Language features</p>
<ul>
<li>goroutine, channel, concurrency (concurrency is not parallelism), error group, singleflight etc. context.</li>
<li>generic, never used it directly after release ...<!-- -->
<ul>
<li><a href="https://go.dev/tour/generics/1" target="_blank" rel="noopener noreferrer">https://go.dev/tour/generics/1</a></li>
<li><a href="https://go.dev/blog/generic-slice-functions" target="_blank" rel="noopener noreferrer">https://go.dev/blog/generic-slice-functions</a></li>
<li><a href="https://go.dev/blog/deconstructing-type-parameters" target="_blank" rel="noopener noreferrer">https://go.dev/blog/deconstructing-type-parameters</a></li>
</ul>
</li>
<li>reflection e.g. struct tag</li>
<li>iterator, yield? <a href="https://go.dev/blog/range-functions" target="_blank" rel="noopener noreferrer">https://go.dev/blog/range-functions</a></li>
<li>embed asset</li>
<li>mod and the new workspace?<!-- -->
<ul>
<li><a href="https://go.dev/blog/toolchain" target="_blank" rel="noopener noreferrer">https://go.dev/blog/toolchain</a></li>
<li><a href="https://go.dev/blog/gonew" target="_blank" rel="noopener noreferrer">https://go.dev/blog/gonew</a> create template</li>
</ul>
</li>
</ul>
<p>Patterns and Libraries</p>
<ul>
<li>logging, there is a new interface for structured log<!-- -->
<ul>
<li><a href="https://go.dev/blog/slog" target="_blank" rel="noopener noreferrer">https://go.dev/blog/slog</a></li>
</ul>
</li>
<li>error handling, error wrapping is the last update I looked into</li>
</ul>
<p>Network</p>
<ul>
<li>basic usage of standard library, net, net/http</li>
<li>implement application protocols, e.g. http 1, websocket on top of low level network.<!-- -->
<ul>
<li><a href="https://github.com/5m-friday/http-protocol-golang/blob/master/tcp-http/main.go" target="_blank" rel="noopener noreferrer">https://github.com/5m-friday/http-protocol-golang/blob/master/tcp-http/main.go</a></li>
<li><a href="https://gist.github.com/jschaf/93f37aedb5327c54cb356b2f1f0427e3" target="_blank" rel="noopener noreferrer">https://gist.github.com/jschaf/93f37aedb5327c54cb356b2f1f0427e3</a> syscall</li>
<li><a href="https://www.krayorn.com/posts/http-server-go/" target="_blank" rel="noopener noreferrer">https://www.krayorn.com/posts/http-server-go/</a> code crafters <a href="https://codecrafters.io/" target="_blank" rel="noopener noreferrer">https://codecrafters.io/</a></li>
</ul>
</li>
<li>a high level wrapper for quick http client/server scripts</li>
</ul>
<p>Database</p>
<ul>
<li><code>database/sql</code> already forgot it ... don't use it often TBH</li>
<li>sqlite? cgo or write a pure go reader</li>
</ul>
<p>Projects to practice</p>
<ul>
<li>Update <a href="https://github.com/dyweb/gommon" target="_blank" rel="noopener noreferrer">https://github.com/dyweb/gommon</a> with latest language features<!-- -->
<ul>
<li><code>goimports</code> with group support, not sure if there is still such thing after this time</li>
</ul>
</li>
<li>A cli to show time in different timezones, the old <code>pm tm</code> port to standalone app.</li>
<li>A static web server for file sharing with local network on trusted devices</li>
<li>Tennis racket price crawler, we can compare cursor code and human written code</li>
<li>DB? Workflow, data analysis etc.?</li>
<li>? LLM related projects? <a href="https://go.dev/blog/llmpowered" target="_blank" rel="noopener noreferrer">https://go.dev/blog/llmpowered</a></li>
<li><a href="https://github.com/practical-tutorials/project-based-learning?tab=readme-ov-file#go" target="_blank" rel="noopener noreferrer">https://github.com/practical-tutorials/project-based-learning?tab=readme-ov-file#go</a></li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="references">References<a href="https://at15.dev/blog/2024/12/going-back-to-go#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References">​</a></h2>
<ul>
<li><a href="https://go.dev/tour/list" target="_blank" rel="noopener noreferrer">https://go.dev/tour/list</a></li>
<li><a href="https://tour.ardanlabs.com/tour/eng/list" target="_blank" rel="noopener noreferrer">https://tour.ardanlabs.com/tour/eng/list</a></li>
</ul>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="go" term="go"/>
        <category label="series" term="series"/>
        <category label="dyweb" term="dyweb"/>
        <category label="gommon" term="gommon"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Send Gmail using Go SMTP]]></title>
        <id>https://at15.dev/blog/2024/12/send-gmail-using-go-smtp</id>
        <link href="https://at15.dev/blog/2024/12/send-gmail-using-go-smtp"/>
        <updated>2024-12-07T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to send email using Go's net/smtp package with Gmail application password.]]></summary>
        <content type="html"><![CDATA[<p>How to send email using Go's <code>net/smtp</code> package with Gmail application password.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="background">Background<a href="https://at15.dev/blog/2024/12/send-gmail-using-go-smtp#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background">​</a></h2>
<p>I want to send notification to myself in some scripts I wrote in go (golang). Go offers builtin support for SMTP in <a href="https://pkg.go.dev/net/smtp" target="_blank" rel="noopener noreferrer">net/smtp</a> and Gmail supports <a href="https://support.google.com/a/answer/176600?hl=en" target="_blank" rel="noopener noreferrer">smtp</a> using password. Gmail also supports <a href="https://developers.google.com/gmail/imap/imap-smtp" target="_blank" rel="noopener noreferrer">oauth2</a> but it is more complicated so I stick with password for now.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-code">The code<a href="https://at15.dev/blog/2024/12/send-gmail-using-go-smtp#the-code" class="hash-link" aria-label="Direct link to The code" title="Direct link to The code">​</a></h2>
<p>Based on <a href="https://gist.github.com/jpillora/cb46d183eca0710d909a" target="_blank" rel="noopener noreferrer">https://gist.github.com/jpillora/cb46d183eca0710d909a</a> and copilot. Full code in <a href="https://github.com/dyweb/blog-code/blob/main/2024-12-07-send-gmail-using-go-smtp/main.go" target="_blank" rel="noopener noreferrer">dyweb/blog-code</a></p>
<ul>
<li>auth is <code>PlainAuth</code> with empty identity and app password. Using google account's password directly does not work. Create app password at <a href="https://myaccount.google.com/apppasswords" target="_blank" rel="noopener noreferrer">https://myaccount.google.com/apppasswords</a> instead.</li>
<li><a href="https://pkg.go.dev/net/smtp#SendMail" target="_blank" rel="noopener noreferrer">SendMail</a> calls auth and dial in one function call, don't need to create <code>Client</code> and do <code>Auth</code> explicitly.</li>
<li>Use <code>smtp.gmail.com:587</code>, <code>587</code> is for TLS, which more up to date than <code>465</code> (SSL) and <code>25</code> <a href="https://www.cloudflare.com/learning/email-security/smtp-port-25-587/" target="_blank" rel="noopener noreferrer">https://www.cloudflare.com/learning/email-security/smtp-port-25-587/</a></li>
<li>Per spec, new line should be CRLF i.e. <code>\r\n</code> but seems <code>\n</code> works for Gmail. There should be an empty line between header and body.</li>
</ul>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">package</span><span class="token plain"> main</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token string" style="color:#e3116c">"fmt"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token string" style="color:#e3116c">"log"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token string" style="color:#e3116c">"net/smtp"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token string" style="color:#e3116c">"os"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// export GMAIL_APP_PASSWORD=your_app_password</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// go run main.go foo@gmail.com bar@gmail.com</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">main</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	from </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Args</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	to </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Args</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token comment" style="color:#999988;font-style:italic">// Create password at https://myaccount.google.com/apppasswords for foo@gmail.com</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	appPassword </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Getenv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"GMAIL_APP_PASSWORD"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">from</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> to</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> appPassword</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Fatal</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// https://gist.github.com/jpillora/cb46d183eca0710d909a</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">from</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> to</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> appPassword </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	auth </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> smtp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">PlainAuth</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		from</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		appPassword</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token string" style="color:#e3116c">"smtp.gmail.com"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token comment" style="color:#999988;font-style:italic">// https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_transport_example</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	subject </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"This subject"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	body </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"This first line\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token string" style="color:#e3116c">"This second line\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token string" style="color:#e3116c">"Here is a link https://google.com"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	msg </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"From: "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> from </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token string" style="color:#e3116c">"To: "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> to </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token string" style="color:#e3116c">"Subject: "</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> subject </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"\n\n"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		body</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token comment" style="color:#999988;font-style:italic">// https://www.cloudflare.com/learning/email-security/smtp-port-25-587/</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> smtp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">SendMail</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"smtp.gmail.com:587"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> auth</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> from</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">to</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token function" style="color:#d73a49">byte</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">msg</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">		</span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> fmt</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Errorf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"send email to %s failed: %w"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> to</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	log</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Printf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Sent email from %s to %s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> from</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> to</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="go" term="go"/>
        <category label="email" term="email"/>
        <category label="short" term="short"/>
        <category label="tutorial" term="tutorial"/>
        <category label="gmail" term="gmail"/>
        <category label="google" term="google"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Build landing page using TailwindUI and Next.js]]></title>
        <id>https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs</id>
        <link href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs"/>
        <updated>2024-04-20T12:00:00.000Z</updated>
        <summary type="html"><![CDATA[My tennis coach wants a landing page for his company, I 'built' one using TailwindUi and Next.js in one day.]]></summary>
        <content type="html"><![CDATA[<p>My tennis coach wants a landing page for his company, I 'built' one using TailwindUi and Next.js in one day.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-tailwindui">Why TailwindUI<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#why-tailwindui" class="hash-link" aria-label="Direct link to Why TailwindUI" title="Direct link to Why TailwindUI">​</a></h2>
<p>Last time I wrote a website outside work I was still using BootStrap with Angular1.
Most companies I worked for have internal frameworks and design systems so there is not much to think about.
For my blog site I just used docsaursus and it ships with its own UI framework <a href="https://infima.dev/" target="_blank" rel="noopener noreferrer">Infima</a>.
For a landing page I need something fancy like all the cool kids.</p>
<p>I heard about tailwindcss a lot, google around and found their paid product <a href="https://tailwindui.com/" target="_blank" rel="noopener noreferrer">TailwindUI</a>.
For me, I want a template with a lot of built-in components, I don't want to learn how to build my own design
systems from scratch. The price (after tax) is about $400 CAD, which is not cheap. But considering the time I
saved from searching free alternative and build equivalent components on my own. I feel spend the money is better
than pounding my head on the wall for a week and giving up with a unfinished website.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-nextjs">Why Next.js<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#why-nextjs" class="hash-link" aria-label="Direct link to Why Next.js" title="Direct link to Why Next.js">​</a></h2>
<p>I actually never used Next.js before, I tried gatsby and docusaurus for my blog site. The main reason is TailwindUI
offers template using Next.js. All I want is copy and paste, plus there is support for Next.js in cloudflare pages,
so I don't need to learn Vercel and deploy things in two places.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-building-process">The building process<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#the-building-process" class="hash-link" aria-label="Direct link to The building process" title="Direct link to The building process">​</a></h2>
<p>Or we should call it the copy and paste process...</p>
<ul>
<li>First buy the product, I chose all access. I feel they just price the individual component/templates in a way that
make you want to buy the all access version.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="read-the-doc">Read the doc<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#read-the-doc" class="hash-link" aria-label="Direct link to Read the doc" title="Direct link to Read the doc">​</a></h3>
<ul>
<li>The <a href="https://tailwindui.com/documentation" target="_blank" rel="noopener noreferrer">official doc</a> doesn't say much, just what you should install
via <code>npm install</code>, fonts etc.</li>
<li>Each <a href="https://tailwindui.com/templates" target="_blank" rel="noopener noreferrer">template</a> offers a zip download, which provides javascript and typescript
version. I choose typescript.</li>
<li>Each <a href="https://tailwindui.com/components/marketing/sections/team-sections" target="_blank" rel="noopener noreferrer">component</a> offers the code to copy and
paste, though it is not full code. While there are multiple items in previews, code snippet only have one item data
and a for loop.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="init-new-project">Init new project<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#init-new-project" class="hash-link" aria-label="Direct link to Init new project" title="Direct link to Init new project">​</a></h3>
<p>I plan to deploy using cloudflare pages, so I created initial Next.js project.
See <a href="https://at15.dev/blog/2024/04/connect-github-repo-to-cloudflare-pages">Connect GitHub repo to Cloudflare Pages</a>
If you are using Vercel, you should be able to use the template directly by pushing it to a github repo
and configuring Vercel accordingly.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="copy-the-template">Copy the template<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#copy-the-template" class="hash-link" aria-label="Direct link to Copy the template" title="Direct link to Copy the template">​</a></h3>
<p>First run my empty Next.js project and the template project side by side.</p>
<p><a href="https://stackoverflow.com/questions/60147499/how-to-set-port-in-next-js" target="_blank" rel="noopener noreferrer">Set different port in <code>package.json</code></a></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "scripts": {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    "dev": "next dev -p 4020",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    "build": "next build",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    "start": "next start -p 4020",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ",,,": "...."</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The missing dependencies in pages</p>
<ul>
<li><code>tailwindcss@latest</code>, cloudflare uses <code>3.3</code></li>
<li><a href="https://www.npmjs.com/package/@tailwindcss/forms" target="_blank" rel="noopener noreferrer">@tailwindcss/forms</a> to reset form styles</li>
<li><code>@headlessui/react</code></li>
<li><code>@headlessui/tailwindcss</code></li>
<li><a href="https://www.npmjs.com/package/clsx" target="_blank" rel="noopener noreferrer">clsx</a> for construct className</li>
</ul>
<p>Cloudflare also have api for workers (edge function), but I am not using it right now, so I will keep the existing hello
world.</p>
<p>Then copy everything to the <code>src</code> folder, you should see everything in the template working in you cloudflare created
repo.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="copy-components">Copy components<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#copy-components" class="hash-link" aria-label="Direct link to Copy components" title="Direct link to Copy components">​</a></h3>
<p>For example, I want the team section on the landing page.</p>
<ul>
<li>Go to <a href="https://tailwindui.com/components/marketing/sections/team-sections" target="_blank" rel="noopener noreferrer">team sections</a></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="update-title-and-meta">Update title and meta<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#update-title-and-meta" class="hash-link" aria-label="Direct link to Update title and meta" title="Direct link to Update title and meta">​</a></h3>
<ul>
<li>Update <code>app/layout.tsx</code>
to <a href="https://nextjs.org/docs/app/building-your-application/optimizing/metadata" target="_blank" rel="noopener noreferrer">change the metadata</a></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="add-a-new-page">Add a new page<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#add-a-new-page" class="hash-link" aria-label="Direct link to Add a new page" title="Direct link to Add a new page">​</a></h3>
<ul>
<li>Follow tutorial <a href="https://nextjs.org/learn/dashboard-app/creating-layouts-and-pages" target="_blank" rel="noopener noreferrer">https://nextjs.org/learn/dashboard-app/creating-layouts-and-pages</a></li>
<li>Create <code>app/anotherpage/page.tsx</code></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="deploy">Deploy<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#deploy" class="hash-link" aria-label="Direct link to Deploy" title="Direct link to Deploy">​</a></h3>
<p>Git push.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="https://at15.dev/blog/2024/04/build-landing-page-using-tailwindui-and-nextjs#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2>
<p>In the end it took 3 hours to have the MVP going live. If I were still back at school,
I would definitely pick some free UI toolkits, build the layout and components step by step for a week.
After working for a couple of years and gave up on multiple side projects.
I feel delivery is more important, if myself and users cannot see a viable product in a reasonable time.
We all gave up... The timely positive feedback matters. The mindset of building something is different
from learning for fun. When building a product, focus should be on the unique part of the project,
this applies both to product itself and its tech stack.</p>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="react" term="react"/>
        <category label="tailwindui" term="tailwindui"/>
        <category label="nextjs" term="nextjs"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Connect GitHub Repo to Cloudflare Pages]]></title>
        <id>https://at15.dev/blog/2024/04/connect-github-repo-to-cloudflare-pages</id>
        <link href="https://at15.dev/blog/2024/04/connect-github-repo-to-cloudflare-pages"/>
        <updated>2024-04-20T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[You have to connect to github before creating the page. You cannot connect on existing page.]]></summary>
        <content type="html"><![CDATA[<p>You have to connect to github before creating the page. You cannot connect on existing page.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-connect-to-github">Why connect to GitHub?<a href="https://at15.dev/blog/2024/04/connect-github-repo-to-cloudflare-pages#why-connect-to-github" class="hash-link" aria-label="Direct link to Why connect to GitHub?" title="Direct link to Why connect to GitHub?">​</a></h2>
<p>When using cloudflare pages, you can publish your build manually using the wrangler cli.
This works when you rarely update the site. However, if you just want to write stuff,
push to github and let it deploy automatically like github pages and netlify, you need to connect to github.</p>
<p>The main annoyance is that you cannot connect to github after creating the page. However, when you run
the cli to create the initial code e.g. <code>npm create cloudflare@latest my-next-app -- --framework=next</code>
it will create the page automatically. So you have to first create a github repo, and connect to it in the pages
dashboard to create the pages, then add the actual code.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="steps">Steps<a href="https://at15.dev/blog/2024/04/connect-github-repo-to-cloudflare-pages#steps" class="hash-link" aria-label="Direct link to Steps" title="Direct link to Steps">​</a></h2>
<ul>
<li>Create a github repo, just create a empty one on github.com like <code>myapp</code></li>
<li>When creating the page repo set <code>--deploy false</code>. For example, run the following in <code>~/dev</code></li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> create cloudflare@latest myapp -- </span><span class="token parameter variable" style="color:#36acaa">--framework</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">next </span><span class="token parameter variable" style="color:#36acaa">--deploy</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ul>
<li>In <code>~/dev/myapp</code> run the following to preview the website</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> run dev</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ul>
<li>Push to github, the <code>c3</code> cli already did git commit, the branch is already <code>main</code> so you don't need to rename it
using <code>git branch -M main</code></li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">git</span><span class="token plain"> remote </span><span class="token function" style="color:#d73a49">add</span><span class="token plain"> origin git@github.com:yourname/myapp.git</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">git</span><span class="token plain"> push </span><span class="token parameter variable" style="color:#36acaa">-u</span><span class="token plain"> origin main</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<ul>
<li>Create Application from <code>Workers &amp; Pages</code> sidebar<!-- -->
<ul>
<li>Select <code>Pages</code> tab, default is Workers</li>
<li>Connect to Git</li>
<li>Select the repo</li>
<li>Select the framework you used when creating the app, e.g. <code>next</code></li>
</ul>
</li>
</ul>
<p>Then cloudflare will do the first deploy and subsequent deploys when you push to github's main branch.</p>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="cloudflare" term="cloudflare"/>
        <category label="pages" term="pages"/>
        <category label="github" term="github"/>
        <category label="netlify" term="netlify"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Learn Python with Friend: The Beginning]]></title>
        <id>https://at15.dev/blog/2024/01/learn-python-with-friend</id>
        <link href="https://at15.dev/blog/2024/01/learn-python-with-friend"/>
        <updated>2024-01-06T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[My friend working at a bank wanted to learn Python and I (using Java at work) wanted to learn Python again for machine]]></summary>
        <content type="html"><![CDATA[<p>My friend working at a bank wanted to learn Python and I (using Java at work) wanted to learn Python again for machine
learning. I thought I could learn python better by teaching another person. I figured it might help people with similar
background (e.g. teach your partner who wants to try software related jobs), so I decided to write a
series of blog posts to document the 'course' I designed and material used.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="syllabus">Syllabus<a href="https://at15.dev/blog/2024/01/learn-python-with-friend#syllabus" class="hash-link" aria-label="Direct link to Syllabus" title="Direct link to Syllabus">​</a></h2>
<p>There are many python tutorial, book and open courses online, but I want to design a course that is more related to my
friend's background (working at bank) so he can practice using things he learned and find the knowledge useful and get
into a positive feedback loop. Otherwise, I would be forcing him to recite concepts and telling him these will be used
eventually like my calculus teacher in university.</p>
<p>He learned programming when he was at university a decade ago and now he only remembers HTML... So the plan is to
start from the very basic and cover the daily tooling of a software engineer such as using terminal, git/github, writing
markdown. Tooling related with programming is a very curial part that I found missing in intro courses back at
university. Version track, write document is not only important for collaborating with other people at work, it is also
important for side project with only one author (and one user...) because you may pick the project up one year later and
need to figure out what your were doing ASAP before you give up again.</p>
<p>The syllabus is divided into chapters, though length of content increases in later chapters. The schedule is 3h offline
sync per week and there was no plan on when we will finish. I will adjust the content based on our progress.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="prepare-development-environment">Prepare development environment<a href="https://at15.dev/blog/2024/01/learn-python-with-friend#prepare-development-environment" class="hash-link" aria-label="Direct link to Prepare development environment" title="Direct link to Prepare development environment">​</a></h3>
<ul>
<li>Terminal<!-- -->
<ul>
<li>common shell commands, <code>ls</code>, <code>cd</code>, <code>cat</code>, <code>vi</code></li>
<li>accepting input and output from command line</li>
<li>make the calculator an executable you can run like other programs</li>
</ul>
</li>
<li>Git and GitHub<!-- -->
<ul>
<li>Create a GitHub account</li>
<li>Track code using git from GUI/terminal</li>
<li>Use GitHub codespace, vscode for both local and remote development</li>
</ul>
</li>
<li>Use the REPL, run python file and Jupiter Notebook</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="language-itself">Language itself<a href="https://at15.dev/blog/2024/01/learn-python-with-friend#language-itself" class="hash-link" aria-label="Direct link to Language itself" title="Direct link to Language itself">​</a></h3>
<ul>
<li>Hello world using a tax/mortgage calculator<!-- -->
<ul>
<li>variable</li>
<li>control flow, if, for</li>
<li>function, argument, return, scope of variables inside function</li>
</ul>
</li>
<li>Basic data structure for tracking a person's monthly expense<!-- -->
<ul>
<li>list</li>
<li>dict</li>
<li>draw plot, if it works w/o Jupyter Notebook then we can still stick with cli</li>
</ul>
</li>
<li>Class, model behavior of a company, e.g. generic employee, manager, worker, ceo etc.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="algorithms">Algorithms<a href="https://at15.dev/blog/2024/01/learn-python-with-friend#algorithms" class="hash-link" aria-label="Direct link to Algorithms" title="Direct link to Algorithms">​</a></h3>
<ul>
<li>brute force</li>
<li>complexity, O(N)</li>
<li>recursion</li>
<li>binary search (for dive and conquer)</li>
<li>dfs/bfs on graph/tree (we can use dict and switch to class later)</li>
<li>LeetCode</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="maintainability">Maintainability<a href="https://at15.dev/blog/2024/01/learn-python-with-friend#maintainability" class="hash-link" aria-label="Direct link to Maintainability" title="Direct link to Maintainability">​</a></h3>
<ul>
<li>Test, unit test</li>
<li>Package, dependency and version management</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="application">Application<a href="https://at15.dev/blog/2024/01/learn-python-with-friend#application" class="hash-link" aria-label="Direct link to Application" title="Direct link to Application">​</a></h3>
<ul>
<li>Web<!-- -->
<ul>
<li>Crawl other website</li>
<li>A simple web service<!-- -->
<ul>
<li>Make previous mortgage calculator a website</li>
<li>use other packages</li>
</ul>
</li>
<li>Use a database, use sqlite because we introduce docker later<!-- -->
<ul>
<li>First start with implementing everything in memory and see if it will cause any issue (gone after restart)</li>
</ul>
</li>
</ul>
</li>
<li>Cloud<!-- -->
<ul>
<li>Docker for development and packaging</li>
<li>Deploy the website to GCP/AWS using free tier</li>
</ul>
</li>
<li>ML, predict person spending, stock price etc.<!-- -->
<ul>
<li>Jupyter Notebook</li>
<li>numpy, pandas</li>
<li>scikit-learn, linear regression etc.</li>
<li>pytorch</li>
<li>llm</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="references">References<a href="https://at15.dev/blog/2024/01/learn-python-with-friend#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References">​</a></h2>
<ul>
<li><a href="https://docs.python.org/3/tutorial/index.html" target="_blank" rel="noopener noreferrer">https://docs.python.org/3/tutorial/index.html</a></li>
<li><a href="https://missing.csail.mit.edu/" target="_blank" rel="noopener noreferrer">https://missing.csail.mit.edu/</a> MIT course on terminal etc.</li>
<li><a href="https://pythontutor.com/" target="_blank" rel="noopener noreferrer">https://pythontutor.com/</a> visualization execution step by step</li>
<li><a href="https://github.com/at15/go-training" target="_blank" rel="noopener noreferrer">https://github.com/at15/go-training</a> My 5 years ago syllabus for go training, never implemented it though</li>
</ul>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="series" term="series"/>
        <category label="python" term="python"/>
        <category label="learn-python-with-friend" term="learn-python-with-friend"/>
        <category label="beginner" term="beginner"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why blog using Docusaurus and Cloudflare Pages]]></title>
        <id>https://at15.dev/blog/2023/12/why-blog-using-docusaurus-and-cloudflare-pages</id>
        <link href="https://at15.dev/blog/2023/12/why-blog-using-docusaurus-and-cloudflare-pages"/>
        <updated>2023-12-21T10:00:00.000Z</updated>
        <summary type="html"><![CDATA[Why I decided to setup a blog using Docusaurus and deploy to Cloudflare Pages instead of using gatsby, netlify, or]]></summary>
        <content type="html"><![CDATA[<p>Why I decided to setup a blog using Docusaurus and deploy to Cloudflare Pages instead of using gatsby, netlify, or
github pages.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-use-static-site-generator-for-blog-when-you-have-medium-wordpress">Why use static site generator for blog when you have Medium, Wordpress?<a href="https://at15.dev/blog/2023/12/why-blog-using-docusaurus-and-cloudflare-pages#why-use-static-site-generator-for-blog-when-you-have-medium-wordpress" class="hash-link" aria-label="Direct link to Why use static site generator for blog when you have Medium, Wordpress?" title="Direct link to Why use static site generator for blog when you have Medium, Wordpress?">​</a></h2>
<p>I have used several hosted blog services such as <a href="https://medium.com/@at15" target="_blank" rel="noopener noreferrer">Medium</a>,
WordPress, <a href="https://ghost.org/" target="_blank" rel="noopener noreferrer">Ghost</a>. These managed platforms do not require any setup
and provide better exposure thanks to existing audiences on the platform.
The main reason I choose to use static site generator is they allow me to write blog as code
in plain markdown, version control the content using git and add style/interactive components (in React) when needed.
The managed platforms offer many things out of box but their customization and API is
limited e.g. <a href="https://github.com/Medium/medium-api-docs" target="_blank" rel="noopener noreferrer">Medium API is no longer supported</a></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-docusaurus-instead-of-gatbsy-hugo-jekyll-etc">Why Docusaurus instead of Gatbsy, Hugo, Jekyll, etc?<a href="https://at15.dev/blog/2023/12/why-blog-using-docusaurus-and-cloudflare-pages#why-docusaurus-instead-of-gatbsy-hugo-jekyll-etc" class="hash-link" aria-label="Direct link to Why Docusaurus instead of Gatbsy, Hugo, Jekyll, etc?" title="Direct link to Why Docusaurus instead of Gatbsy, Hugo, Jekyll, etc?">​</a></h2>
<p>GitHub pages offers Jekyll and I am not a fan of Ruby (working at AWS on region build made the feeling even worse).
Building Jekyll pages locally is much painful compared with Go or NodeJS based static site generator because I
don't setup Ruby toolchain on my own devices.</p>
<p><a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> is in Go but the template syntax and the extensibility is not as good as these React based
ones such as Gatsby and Docusaurus.</p>
<p>Gatsby is the one I planned to start with because it offers a GraphQL API, making building extension and interact with
other languages a breeze. However, after looking at
its <a href="https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog/" target="_blank" rel="noopener noreferrer">blog template</a>, I found there are several things
I need to implement by myself such as <a href="https://www.gatsbyjs.com/docs/adding-tags-and-categories-to-blog-posts/" target="_blank" rel="noopener noreferrer">tag</a>.
I am lazy and want most things out of box with a reasonable default that I customize later.</p>
<p>Then I looked into documentation type of static site generator such as <a href="https://docusaurus.io/" target="_blank" rel="noopener noreferrer">Docusaurus</a>
and <a href="https://vuepress.vuejs.org/" target="_blank" rel="noopener noreferrer">VuePress</a>. I have used VuePress
for <a href="https://github.com/xephonhq/awesome-time-series-database" target="_blank" rel="noopener noreferrer">awesome-time-series-database</a>, but the VuePress blog
plugin's last update time is <a href="https://github.com/vuepress/vuepress-plugin-blog" target="_blank" rel="noopener noreferrer">9 months ago</a> so I tried Docusaurus.
The default docusaurus template support both doc and blog with tags, the blog looks better than gatsby's blog template.
So I decided to use Docuasurus.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-cloudflare-pages-instead-of-netlify-github-pages">Why Cloudflare Pages instead of Netlify, GitHub Pages?<a href="https://at15.dev/blog/2023/12/why-blog-using-docusaurus-and-cloudflare-pages#why-cloudflare-pages-instead-of-netlify-github-pages" class="hash-link" aria-label="Direct link to Why Cloudflare Pages instead of Netlify, GitHub Pages?" title="Direct link to Why Cloudflare Pages instead of Netlify, GitHub Pages?">​</a></h2>
<p>GitHub Pages is the most straightforward to setup when source code of blog is hosted on GitHub and I used to use it
before I switched to Netlify for <a href="https://docs.netlify.com/site-deploys/deploy-previews/" target="_blank" rel="noopener noreferrer">PR preview</a> which
is <a href="https://github.com/orgs/community/discussions/7730" target="_blank" rel="noopener noreferrer">not supported by GitHub Pages</a>. However, Netlify's free plan has
limit on <a href="https://www.netlify.com/pricing/" target="_blank" rel="noopener noreferrer">100GB bandwidth</a> and
then <a href="https://www.netlify.com/pricing/#core-pricing-table" target="_blank" rel="noopener noreferrer">$55 Per 100GB</a>
while <a href="https://community.cloudflare.com/t/cloudflare-bandwidth-limit/447321" target="_blank" rel="noopener noreferrer">Cloudflare Pages has no limit on bandwidth</a>.</p>
<p>Cloudflare Pages does have its very
special <a href="https://developers.cloudflare.com/pages/platform/limits/#files" target="_blank" rel="noopener noreferrer">20,000 files</a> hard limit due
to <a href="https://community.cloudflare.com/t/cloudflare-pages-what-plan-do-we-need-to-exceed-the-20k-asset-limit/408982/2" target="_blank" rel="noopener noreferrer">technical limit</a>.
But as long as I don't copy <code>node_modules</code> into the <code>build</code> directory, it might take years for me to hit that limit with
notes and blogs (116 files right now). btw: You can count the files (including subdirectories)
using <code>find . -type f | wc -l</code>
per <a href="https://unix.stackexchange.com/questions/4105/how-do-i-count-all-the-files-recursively-through-directories" target="_blank" rel="noopener noreferrer">stackoverflow</a>.
If I did hit the limit, I could host (part of) the website on other places (e.g. nginx, object store like R2/S3)
and use (Cloudflare) CDN to serve the content.</p>
<p>In next post I will talk about the actual steps of creating the blog using Docusaurus and configure Cloudflare Pages
with github integration and custom domain.</p>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="docuasurus" term="docuasurus"/>
        <category label="cloudflare" term="cloudflare"/>
        <category label="github" term="github"/>
        <category label="amazon" term="amazon"/>
        <category label="rant" term="rant"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[First Blog Post]]></title>
        <id>https://at15.dev/blog/first-blog-post</id>
        <link href="https://at15.dev/blog/first-blog-post"/>
        <updated>2023-12-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[First blog post using docusaurus]]></summary>
        <content type="html"><![CDATA[<p>First blog post using docusaurus</p>]]></content>
        <author>
            <name>at15</name>
            <uri>https://at15.dev/about</uri>
        </author>
        <category label="docusaurus" term="docusaurus"/>
    </entry>
</feed>