<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Http on WebDevStation</title>
    <link>https://webdevstation.com/tags/http/</link>
    <description>10 articles tagged Http — tutorials, code examples and notes from building real systems, newest first.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <managingEditor>Alex</managingEditor>
    <webMaster>Alex</webMaster>
    <copyright>© 2026 WebDevStation</copyright>
    <lastBuildDate>Tue, 25 Aug 2026 09:00:00 +0200</lastBuildDate>
    <atom:link href="https://webdevstation.com/tags/http/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Rate Limiting Go APIs with golang.org/x/time/rate</title>
      <link>https://webdevstation.com/posts/rate-limiting-go-apis/</link>
      <pubDate>Tue, 25 Aug 2026 09:00:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/rate-limiting-go-apis/</guid>
      <description>Protect your Go HTTP APIs with token bucket rate limiting: per-client limiters, middleware, the standard rate limit headers, client-side throttling and when to move…</description>
      <content:encoded><![CDATA[<p>Somebody eventually points a badly written script at your API. Not maliciously — usually it is a colleague&rsquo;s retry loop with no back-off, or a cron job that fires every minute and takes ninety seconds. Without a limit, one client can consume the capacity you were saving for everyone else. <code>golang.org/x/time/rate</code> handles this in about as much code as it takes to describe, and it is the piece I now add before the first public endpoint ships.</p>
<h2 id="the-token-bucket-in-one-paragraph">The Token Bucket, in One Paragraph</h2>
<p>Picture a bucket that holds <code>b</code> tokens and refills at <code>r</code> tokens per second. Every request takes one token. If the bucket is empty, the request is rejected (or waits). That is the whole model, and it has one property that makes it the right default: <code>b</code> is a <strong>burst</strong> allowance. A client that has been idle can spend its accumulated tokens all at once, then settles into the steady rate. Real traffic is bursty — a page load firing six API calls should not be punished, while a loop firing six hundred should.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;golang.org/x/time/rate&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 10 requests per second, bursts of up to 20.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">limiter</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">NewLimiter</span>(<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">20</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">limiter</span>.<span style="color:#a6e22e">Allow</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// over budget</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Three methods, for three different situations:</p>
<table>
	<thead>
			<tr>
					<th>Method</th>
					<th>Behaviour</th>
					<th>Use it for</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>Allow()</code></td>
					<td>Returns immediately: true or false</td>
					<td>Inbound HTTP — reject with 429</td>
			</tr>
			<tr>
					<td><code>Wait(ctx)</code></td>
					<td>Blocks until a token is free or ctx ends</td>
					<td>Outbound calls you control</td>
			</tr>
			<tr>
					<td><code>Reserve()</code></td>
					<td>Reserves a token, tells you the delay</td>
					<td>When you need to report <code>Retry-After</code></td>
			</tr>
	</tbody>
</table>
<p><code>rate.Limit</code> is a float, so fractional rates work: <code>rate.Every(time.Minute/100)</code> is 100 per minute, and <code>rate.Limit(0.5)</code> is one request every two seconds. <code>rate.Inf</code> disables limiting entirely, which is handy for a per-plan configuration where some tier is unlimited.</p>
<h2 id="a-global-limiter-is-not-enough">A Global Limiter Is Not Enough</h2>
<p>The naive version puts one limiter in front of everything:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">global</span> = <span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">NewLimiter</span>(<span style="color:#ae81ff">100</span>, <span style="color:#ae81ff">200</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">limit</span>(<span style="color:#a6e22e">next</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">Allow</span>() {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;too many requests&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusTooManyRequests</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">next</span>.<span style="color:#a6e22e">ServeHTTP</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This protects your <em>server</em> but not your <em>users</em>: one aggressive client can still eat the entire global budget and everyone else gets 429s. A global limiter is a useful backstop, not a fairness mechanism. What you want is a limiter per client, with the global one behind it.</p>
<h2 id="per-client-limiters">Per-Client Limiters</h2>
<p>Keep a map from client key to limiter, guarded by a mutex, with a janitor that evicts idle entries so the map does not grow forever.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">ratelimit</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;golang.org/x/time/rate&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">client</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">limiter</span>  <span style="color:#f92672">*</span><span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">Limiter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">lastSeen</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Store hands out one limiter per key and forgets keys that go quiet.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Store</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mu</span>      <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">Mutex</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">clients</span> <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">client</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">rate</span>    <span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">Limit</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">burst</span>   <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ttl</span>     <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewStore</span>(<span style="color:#a6e22e">r</span> <span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">Limit</span>, <span style="color:#a6e22e">burst</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">ttl</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Store</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">clients</span>: make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#f92672">*</span><span style="color:#a6e22e">client</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">rate</span>:    <span style="color:#a6e22e">r</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">burst</span>:   <span style="color:#a6e22e">burst</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ttl</span>:     <span style="color:#a6e22e">ttl</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">cleanup</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Limiter returns the limiter for key, creating it on first use.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">Limiter</span>(<span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">Limiter</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">c</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">clients</span>[<span style="color:#a6e22e">key</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">c</span> = <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">client</span>{<span style="color:#a6e22e">limiter</span>: <span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">NewLimiter</span>(<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">rate</span>, <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">burst</span>)}
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">clients</span>[<span style="color:#a6e22e">key</span>] = <span style="color:#a6e22e">c</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">lastSeen</span> = <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">limiter</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">cleanup</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ticker</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">NewTicker</span>(<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">ttl</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">ticker</span>.<span style="color:#a6e22e">Stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">ticker</span>.<span style="color:#a6e22e">C</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Lock</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">clients</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">lastSeen</span>) &gt; <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">ttl</span> {
</span></span><span style="display:flex;"><span>                delete(<span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">clients</span>, <span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">mu</span>.<span style="color:#a6e22e">Unlock</span>()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The mutex is not optional. A bare <code>map[string]*rate.Limiter</code> written from concurrent handlers is a textbook data race, and Go&rsquo;s runtime will happily crash the process with <code>concurrent map writes</code> — the same failure I dug into in <a href="/posts/concurrent-map-writing-and-reading-in-go/">concurrent map writing and reading in Go</a>.</p>
<p>Two design notes. <code>sync.Map</code> is not a better fit here: it is optimised for read-mostly workloads with stable keys, and this map is written on every new client. And an unbounded map is a memory-exhaustion vector if the key is attacker-controlled — hence the TTL. For a hard cap, put an LRU in front of it.</p>
<h2 id="the-middleware">The Middleware</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Middleware</span>(<span style="color:#a6e22e">store</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">next</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">limiter</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">Limiter</span>(<span style="color:#a6e22e">clientKey</span>(<span style="color:#a6e22e">r</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Reserve, rather than Allow, so we can report Retry-After.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">limiter</span>.<span style="color:#a6e22e">Reserve</span>()
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">OK</span>() {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Burst is smaller than the request size; never satisfiable.</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;rate limit misconfigured&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">delay</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">Delay</span>(); <span style="color:#a6e22e">delay</span> &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// We are not going to wait, so give the token back.</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">Cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;Retry-After&#34;</span>, <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(int(<span style="color:#a6e22e">math</span>.<span style="color:#a6e22e">Ceil</span>(<span style="color:#a6e22e">delay</span>.<span style="color:#a6e22e">Seconds</span>()))))
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;RateLimit-Limit&#34;</span>, <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(<span style="color:#a6e22e">limiter</span>.<span style="color:#a6e22e">Burst</span>()))
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;RateLimit-Remaining&#34;</span>, <span style="color:#e6db74">&#34;0&#34;</span>)
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;RateLimit-Reset&#34;</span>, <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">Itoa</span>(int(<span style="color:#a6e22e">math</span>.<span style="color:#a6e22e">Ceil</span>(<span style="color:#a6e22e">delay</span>.<span style="color:#a6e22e">Seconds</span>()))))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusTooManyRequests</span>)
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewEncoder</span>(<span style="color:#a6e22e">w</span>).<span style="color:#a6e22e">Encode</span>(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>                    <span style="color:#e6db74">&#34;error&#34;</span>: <span style="color:#e6db74">&#34;rate limit exceeded&#34;</span>,
</span></span><span style="display:flex;"><span>                })
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">next</span>.<span style="color:#a6e22e">ServeHTTP</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>res.Cancel()</code> is the line people forget. <code>Reserve</code> takes the token immediately; if you then decide not to wait, cancelling returns it to the bucket. Skip it and every rejected request still consumes budget, so a client that trips the limit stays locked out far longer than intended.</p>
<p>This plugs into any router the same way as the handlers in my <a href="/posts/go-middleware-example/">Go middleware example</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">store</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ratelimit</span>.<span style="color:#a6e22e">NewStore</span>(<span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">Limit</span>(<span style="color:#ae81ff">10</span>), <span style="color:#ae81ff">20</span>, <span style="color:#ae81ff">10</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Minute</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">chi</span>.<span style="color:#a6e22e">NewRouter</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Use</span>(<span style="color:#a6e22e">ratelimit</span>.<span style="color:#a6e22e">Middleware</span>(<span style="color:#a6e22e">store</span>))
</span></span></code></pre></div><h2 id="choosing-the-client-key">Choosing the Client Key</h2>
<p>This is where rate limiting is usually got wrong, and it is worth more thought than the algorithm.</p>
<p><strong>Authenticated requests: key on the identity.</strong> An API key or user ID is stable, meaningful, and cannot be spoofed once you have verified the token. If you are issuing JWTs — as in <a href="/posts/user-authentication-with-go-using-jwt-token/">user authentication in Go Echo with JWT</a> — the subject claim is your key.</p>
<p><strong>Anonymous requests: key on the IP, carefully.</strong> <code>r.RemoteAddr</code> behind a proxy is the proxy&rsquo;s address, so every user shares one bucket. But blindly trusting <code>X-Forwarded-For</code> is worse: it is a client-supplied header, and anyone can put whatever they like in it to get a fresh bucket per request.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">clientKey</span>(<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Authenticated callers are keyed on identity.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">userID</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">auth</span>.<span style="color:#a6e22e">UserFrom</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>()); <span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;user:&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">userID</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Only trust the proxy header if the request came from our proxy,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// and take the address the proxy appended — the rightmost hop.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ip</span>, <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">net</span>.<span style="color:#a6e22e">SplitHostPort</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">RemoteAddr</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">isTrustedProxy</span>(<span style="color:#a6e22e">ip</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">xff</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Header</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;X-Forwarded-For&#34;</span>); <span style="color:#a6e22e">xff</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">parts</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">xff</span>, <span style="color:#e6db74">&#34;,&#34;</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">ip</span> = <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">TrimSpace</span>(<span style="color:#a6e22e">parts</span>[len(<span style="color:#a6e22e">parts</span>)<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>])
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;ip:&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">ip</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The rightmost entry is the one your own proxy added; everything to its left came from the client and is unverifiable. If your platform provides a trusted header — Cloudflare&rsquo;s <code>CF-Connecting-IP</code>, or the standard <code>Forwarded</code> from a proxy you control — prefer it.</p>
<p>One more refinement: not all endpoints are equal. <code>POST /reports/export</code> might cost a hundred times what <code>GET /health</code> does. <code>AllowN</code> and <code>ReserveN</code> let you charge by cost:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cost</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Method</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPost</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">HasPrefix</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Path</span>, <span style="color:#e6db74">&#34;/reports&#34;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">cost</span> = <span style="color:#ae81ff">25</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">res</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">limiter</span>.<span style="color:#a6e22e">ReserveN</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>(), <span style="color:#a6e22e">cost</span>)
</span></span></code></pre></div><p>Just keep the burst at least as large as your most expensive operation, or <code>res.OK()</code> returns false forever and that endpoint becomes permanently unreachable.</p>
<h2 id="limiting-yourself-too">Limiting Yourself, Too</h2>
<p>Rate limiting is not only defensive. When you are the client of somebody else&rsquo;s API, respecting their limit proactively beats absorbing 429s and retrying. <code>Wait</code> is built for this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Client</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>    <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Client</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">limiter</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">Limiter</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewClient</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Client</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">http</span>:    <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Client</span>{<span style="color:#a6e22e">Timeout</span>: <span style="color:#ae81ff">10</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>},
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// The upstream allows 5 requests/second; stay under it.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">limiter</span>: <span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">NewLimiter</span>(<span style="color:#ae81ff">5</span>, <span style="color:#ae81ff">5</span>),
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Client</span>) <span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">req</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Response</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Blocks until a token is available, or ctx is cancelled.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">limiter</span>.<span style="color:#a6e22e">Wait</span>(<span style="color:#a6e22e">ctx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;rate limiter: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">WithContext</span>(<span style="color:#a6e22e">ctx</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>Wait</code> returns an error if the context is cancelled or if its deadline arrives before a token would — so a caller that has already given up never sits in the queue. That is the <a href="/posts/understanding-golang-context/">context</a> machinery doing exactly what it is for.</p>
<p>This composes neatly with a bounded worker pool: the pool caps how many requests are <em>in flight</em>, the limiter caps how many <em>start per second</em>. They constrain different things and you usually want both, as I covered in <a href="/posts/worker-pools-in-go-with-errgroup/">worker pools in Go with errgroup</a>.</p>
<h2 id="where-this-approach-stops-working">Where This Approach Stops Working</h2>
<p>Be honest about the limits of an in-process limiter.</p>
<p><strong>It is per instance.</strong> Three replicas with a limit of 10/s allow 30/s in total, and a client bouncing between them gets a fresh bucket each time. For a real global limit you need shared state — Redis with a Lua script that does the token accounting atomically, or a limiter at the edge.</p>
<p><strong>It is lost on restart.</strong> Every deploy resets every bucket. Usually fine; occasionally not.</p>
<p><strong>It costs you a request.</strong> The request still reaches your process, gets routed, and allocates before being rejected. Under a genuine flood, that is exactly the work you cannot afford — which is why volumetric protection belongs at the CDN or load balancer, not in your handler.</p>
<p>My rule of thumb: <strong>application limits enforce fairness and per-plan quotas; edge limits absorb abuse.</strong> They solve different problems and you want both. The nginx layer is a natural place for the coarse one, and it can be surprisingly nuanced — the <a href="/posts/how-to-make-nginx-cookie-aware/">cookie-aware caching</a> tricks work the same way for keying <code>limit_req</code> zones.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">limit_req_zone</span> $binary_remote_addr <span style="color:#e6db74">zone=api:10m</span> <span style="color:#e6db74">rate=100r/s</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">location</span> <span style="color:#e6db74">/api/</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">limit_req</span> <span style="color:#e6db74">zone=api</span> <span style="color:#e6db74">burst=200</span> <span style="color:#e6db74">nodelay</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://backend</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="testing-it">Testing It</h2>
<p>Rate limiting is easy to test badly, because <code>time.Now()</code> is involved. Keep the rates small and explicit rather than sleeping through real seconds:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestLimiterRejectsBurst</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">store</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ratelimit</span>.<span style="color:#a6e22e">NewStore</span>(<span style="color:#a6e22e">rate</span>.<span style="color:#a6e22e">Limit</span>(<span style="color:#ae81ff">1</span>), <span style="color:#ae81ff">3</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Minute</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">h</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ratelimit</span>.<span style="color:#a6e22e">Middleware</span>(<span style="color:#a6e22e">store</span>)(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) { <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>) },
</span></span><span style="display:flex;"><span>    ))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">codes</span> <span style="color:#f92672">:=</span> make([]<span style="color:#66d9ef">int</span>, <span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">codes</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">req</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRequest</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>, <span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">RemoteAddr</span> = <span style="color:#e6db74">&#34;203.0.113.7:1234&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">rec</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">httptest</span>.<span style="color:#a6e22e">NewRecorder</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">ServeHTTP</span>(<span style="color:#a6e22e">rec</span>, <span style="color:#a6e22e">req</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">codes</span>[<span style="color:#a6e22e">i</span>] = <span style="color:#a6e22e">rec</span>.<span style="color:#a6e22e">Code</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Burst of 3 succeeds, the rest are rejected.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">want</span> <span style="color:#f92672">:=</span> []<span style="color:#66d9ef">int</span>{<span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">429</span>, <span style="color:#ae81ff">429</span>}
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">slices</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">codes</span>, <span style="color:#a6e22e">want</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;got %v, want %v&#34;</span>, <span style="color:#a6e22e">codes</span>, <span style="color:#a6e22e">want</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then confirm the behaviour under real load before you trust the number. Pointing a load test at the endpoint and watching the ratio of 200s to 429s tells you whether your limit matches the traffic you actually get — the setup in <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">an easy way to load test your web apps</a> is enough for this.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li>Per-client limiters, not one global bucket, with a global one as backstop.</li>
<li>Key on identity when authenticated; on a <em>verified</em> IP otherwise.</li>
<li>Evict idle limiters so the map cannot grow without bound.</li>
<li><code>res.Cancel()</code> whenever you reject instead of waiting.</li>
<li>Send <code>Retry-After</code> and <code>RateLimit-*</code> headers so good clients can behave.</li>
<li>Burst at least as large as your most expensive weighted operation.</li>
<li><code>Wait(ctx)</code> on the client side of other people&rsquo;s APIs.</li>
<li>Volumetric protection at the edge, fairness in the application.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p><code>golang.org/x/time/rate</code> is one of those packages that does exactly one thing and does it without ceremony. The algorithm is not the hard part — picking a sensible client key, giving tokens back when you reject, and being clear about what an in-process limiter can and cannot promise is where the real work is. Get those right and a single misbehaving script stops being everybody else&rsquo;s problem. And if the API you are the client of happens to be a model provider, an agent loop is a remarkably efficient way to find its limits — see <a href="/posts/tool-use-in-go-agent-loop/">tool use in Go</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Web Development</category>
      <category>Backend Development</category>
      <category>Security</category>
    </item>
    <item>
      <title>Graceful Shutdown in Go Web Services: Stop Dropping Requests on Deploy</title>
      <link>https://webdevstation.com/posts/graceful-shutdown-in-go-web-services/</link>
      <pubDate>Tue, 11 Aug 2026 18:40:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/graceful-shutdown-in-go-web-services/</guid>
      <description>How to shut down a Go HTTP server without dropping in-flight requests: signal.NotifyContext, Server.Shutdown, draining background workers, and the timeouts that make…</description>
      <content:encoded><![CDATA[<p>The first time I deployed a Go service behind a rolling update, our error dashboard lit up on every single release. Nothing was broken — the new version was fine, the old version was fine. The problem was the half-second in between, where the old process died mid-request and a few dozen users got a connection reset. Fixing it took about twenty lines of code, and I have copied those twenty lines into every service since.</p>
<h2 id="what-actually-happens-on-shutdown">What Actually Happens on Shutdown</h2>
<p>When your orchestrator wants a container gone, it sends <code>SIGTERM</code> and starts a countdown. If the process is still alive when the countdown ends, it gets <code>SIGKILL</code>, which cannot be caught.</p>
<p>A Go program with no signal handling takes the default action for <code>SIGTERM</code>: immediate termination. Every open connection is severed. Any request that was 90% done is simply gone — the client sees a reset, your retry budget takes the hit, and if that request was a payment you now have a support ticket.</p>
<p>Graceful shutdown means using the window between <code>SIGTERM</code> and <code>SIGKILL</code> to:</p>
<ol>
<li>Stop accepting new connections.</li>
<li>Let in-flight requests finish.</li>
<li>Drain background workers.</li>
<li>Close databases, caches and queues.</li>
<li>Exit before the countdown runs out.</li>
</ol>
<h2 id="the-twenty-lines">The Twenty Lines</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;log/slog&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os/signal&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;syscall&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Addr</span>:              <span style="color:#e6db74">&#34;:8080&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Handler</span>:           <span style="color:#a6e22e">newRouter</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ReadHeaderTimeout</span>: <span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ReadTimeout</span>:       <span style="color:#ae81ff">15</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">WriteTimeout</span>:      <span style="color:#ae81ff">30</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">IdleTimeout</span>:       <span style="color:#ae81ff">60</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ctx is cancelled the first time we receive SIGINT or SIGTERM.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">stop</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">NotifyContext</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Interrupt</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGTERM</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;listening&#34;</span>, <span style="color:#e6db74">&#34;addr&#34;</span>, <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Addr</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// ListenAndServe always returns a non-nil error; ErrServerClosed</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// is the expected one after Shutdown.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">ListenAndServe</span>(); !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;listen failed&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">stop</span>() <span style="color:#75715e">// restore default handling: a second Ctrl-C now kills us</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;shutdown signal received, draining&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;graceful shutdown failed, forcing close&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;shutdown complete&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That is the whole pattern. A few details are load-bearing:</p>
<p><strong><code>signal.NotifyContext</code> instead of a channel.</strong> Since Go 1.16 this gives you a <code>context.Context</code> that cancels on the listed signals, which composes with everything else that already takes a context. If contexts and cancellation are new to you, <a href="/posts/understanding-golang-context/">understanding Golang context</a> is the background reading.</p>
<p><strong>Calling <code>stop()</code> after the first signal.</strong> It restores the default signal behaviour, so an impatient operator pressing Ctrl-C a second time gets an immediate exit instead of being ignored.</p>
<p><strong>A fresh context for <code>Shutdown</code>.</strong> Deriving it from <code>ctx</code> would be a bug: <code>ctx</code> is already cancelled, so <code>Shutdown</code> would return instantly and drain nothing.</p>
<p><strong>Checking for <code>ErrServerClosed</code>.</strong> <code>ListenAndServe</code> returns it on a clean shutdown. Treating that as a failure produces a scary log line on every normal deploy.</p>
<h2 id="what-shutdown-does-and-does-not-do">What Shutdown Does and Does Not Do</h2>
<p><code>Server.Shutdown</code> closes all open listeners, closes idle connections, and then waits for active ones to become idle. It returns when everything is drained or when its context expires — whichever comes first.</p>
<p>What it does <strong>not</strong> cover:</p>
<ul>
<li><strong>Hijacked connections</strong>, including WebSockets. <code>Shutdown</code> does not wait for them, and it does not close them. You have to track and close them yourself.</li>
<li><strong>Background goroutines</strong> you started outside the request path. Nothing knows about them.</li>
<li><strong>Long-polling or streaming responses.</strong> These are &ldquo;active&rdquo; for as long as they stream, so they will hold the drain open until your timeout fires.</li>
</ul>
<p>For WebSockets, the usual approach is to register a callback that broadcasts a close frame:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">RegisterOnShutdown</span>(<span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">hub</span>.<span style="color:#a6e22e">CloseAll</span>(<span style="color:#a6e22e">websocket</span>.<span style="color:#a6e22e">CloseServiceRestart</span>, <span style="color:#e6db74">&#34;server restarting&#34;</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p><code>RegisterOnShutdown</code> callbacks run in their own goroutines as soon as <code>Shutdown</code> starts, so they get the whole drain window to do their work.</p>
<h2 id="draining-background-workers-too">Draining Background Workers Too</h2>
<p>Most real services do more than serve HTTP. If you have consumers, cron loops, or a queue like the one in <a href="/posts/simple-queue-implementation-in-golang/">my simple queue implementation</a>, they need to finish too. <code>errgroup</code> keeps this readable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;golang.org/x/sync/errgroup&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{<span style="color:#a6e22e">Addr</span>: <span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#a6e22e">Handler</span>: <span style="color:#a6e22e">newRouter</span>()}
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">queue</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">NewQueue</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>, <span style="color:#a6e22e">gCtx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">errgroup</span>.<span style="color:#a6e22e">WithContext</span>(<span style="color:#a6e22e">ctx</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 1. Serve HTTP.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">ListenAndServe</span>(); !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;http server: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 2. Consume the queue until the group context is cancelled.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">queue</span>.<span style="color:#a6e22e">Consume</span>(<span style="color:#a6e22e">gCtx</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 3. When anything cancels gCtx — a signal, or a failure in another</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//    goroutine — drain the HTTP server.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Go</span>(<span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">gCtx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">g</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">stop</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">signal</span>.<span style="color:#a6e22e">NotifyContext</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Interrupt</span>, <span style="color:#a6e22e">syscall</span>.<span style="color:#a6e22e">SIGTERM</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">stop</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">ctx</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;service stopped&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;service stopped cleanly&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The nice property here is that failure propagates in both directions. A signal drains the HTTP server; a fatal error in the queue consumer also drains the HTTP server, because <code>errgroup.WithContext</code> cancels <code>gCtx</code> as soon as any goroutine returns an error.</p>
<p>The order of shutdown matters, and it is the reverse of startup: stop accepting work, finish what you have, then close the things that work depends on. Closing your database pool before draining HTTP guarantees a burst of errors from requests that were nearly done.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// After g.Wait() returns, nothing is still using these.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Close</span>()
</span></span></code></pre></div><h2 id="the-load-balancer-problem">The Load Balancer Problem</h2>
<p>Here is the part that surprises people: even a perfectly graceful process can drop requests.</p>
<p>Between the moment your pod receives <code>SIGTERM</code> and the moment the load balancer stops sending it traffic, there is a gap. Endpoint updates propagate asynchronously — through the API server, to kube-proxy or an ingress controller, and finally to the actual routing table. During that gap the balancer is still sending new connections to a server that has already closed its listener. Those connections are refused.</p>
<p>The fix is to keep serving for a few seconds <em>after</em> the signal arrives:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;signal received, waiting for load balancer to deregister&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Keep serving while the endpoint removal propagates.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">shutdownCtx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">20</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">shutdownCtx</span>)
</span></span></code></pre></div><p>It feels wrong to <code>sleep</code> on purpose, but it is the standard remedy, and Kubernetes has a hook for exactly this so you do not need it in your code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">lifecycle</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">preStop</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">exec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">command</span>: [<span style="color:#e6db74">&#34;/bin/sh&#34;</span>, <span style="color:#e6db74">&#34;-c&#34;</span>, <span style="color:#e6db74">&#34;sleep 5&#34;</span>]
</span></span><span style="display:flex;"><span><span style="color:#f92672">terminationGracePeriodSeconds</span>: <span style="color:#ae81ff">45</span>
</span></span></code></pre></div><p><code>preStop</code> runs <em>before</em> <code>SIGTERM</code> is sent, while the pod is already being removed from the endpoints list. By the time your process sees the signal, traffic has stopped arriving.</p>
<p>Whichever way you do it, keep the arithmetic straight:</p>
<pre tabindex="0"><code>preStop sleep (5s) + drain timeout (20s) + close time (2s) &lt; terminationGracePeriodSeconds (45s)
</code></pre><p>If the total exceeds the grace period, you get <code>SIGKILL</code> mid-drain and you are back where you started. Give yourself real headroom — the default grace period is 30 seconds, which is not much once a slow request is in flight.</p>
<p>A readiness probe that starts failing on <code>SIGTERM</code> achieves the same thing more precisely:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ready</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">Bool</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() { <span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#66d9ef">true</span>) }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// /readyz</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">readyz</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Load</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;shutting down&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusServiceUnavailable</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// On signal, before draining:</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ready</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#66d9ef">false</span>)
</span></span></code></pre></div><p>Keep <code>/healthz</code> (liveness) returning 200 the whole time — if liveness fails during shutdown, the kubelet may kill the container instead of letting it drain.</p>
<h2 id="docker-gotchas">Docker Gotchas</h2>
<p>Two container-level mistakes will silently defeat everything above.</p>
<p><strong>Shell-form <code>CMD</code> makes your process PID 2.</strong> Written as <code>CMD ./server</code>, Docker runs <code>/bin/sh -c ./server</code>. The shell is PID 1, receives <code>SIGTERM</code>, and does not forward it. Your server never hears a thing.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#75715e"># Wrong — signals stop at the shell</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> ./server<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># Right — exec form, your binary is PID 1</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;./server&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p><strong><code>docker stop</code> waits 10 seconds by default.</strong> If your drain window is 20 seconds, you will be killed halfway through. Raise it: <code>docker stop -t 45</code>, or <code>stop_grace_period: 45s</code> in Compose.</p>
<h2 id="verifying-it-works">Verifying It Works</h2>
<p>Do not take it on faith — this is easy to test. Add a slow endpoint, start a request, and signal the process mid-flight:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/slow&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">After</span>(<span style="color:#ae81ff">10</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#e6db74">&#34;finished\n&#34;</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>().<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// The client gave up; Shutdown does not cancel request contexts.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>./server &amp;
</span></span><span style="display:flex;"><span>curl -s localhost:8080/slow &amp;     <span style="color:#75715e"># starts a 10s request</span>
</span></span><span style="display:flex;"><span>sleep <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>kill -TERM %1                     <span style="color:#75715e"># signal while it is in flight</span>
</span></span></code></pre></div><p>A correct implementation prints <code>finished</code> after ten seconds and then exits. A broken one prints nothing and the curl reports a reset connection.</p>
<p>For the same check under real traffic, point a load test at the service and restart it mid-run — the technique from <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">an easy way to load test your web apps</a> works well here. With graceful shutdown in place your error count during a restart should be exactly zero; without it, you will see the exact number of requests that were in flight.</p>
<p>Note what <code>Shutdown</code> does <em>not</em> do: it does not cancel <code>r.Context()</code> for in-flight requests. That is deliberate — the request should be allowed to complete. It also means a handler with no timeout of its own can hold the drain open until your shutdown context expires, which is why the <code>WriteTimeout</code> in the first example matters.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li><code>signal.NotifyContext</code> for <code>SIGINT</code> and <code>SIGTERM</code>.</li>
<li><code>srv.Shutdown</code> with its own fresh, bounded context.</li>
<li><code>errors.Is(err, http.ErrServerClosed)</code> treated as success.</li>
<li>Background workers cancelled through the same context, drained before dependencies close.</li>
<li>Dependencies closed last, in reverse order of startup.</li>
<li>Readiness probe flipped to failing before the drain begins.</li>
<li><code>preStop</code> hook or a deliberate sleep to cover load balancer propagation.</li>
<li>Grace period comfortably larger than the sum of your timeouts.</li>
<li>Exec-form <code>CMD</code> in the Dockerfile.</li>
<li>A test that proves an in-flight request survives a <code>SIGTERM</code>.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Graceful shutdown is one of those features nobody notices when it works — which is precisely the point. Twenty lines in <code>main</code>, a couple of timeouts that add up correctly, and a container that actually forwards signals will turn every deploy from a small burst of errors into a non-event. It is the cheapest reliability win available to a Go service, and worth adding before the next release rather than after the next incident.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
      <category>DevOps</category>
    </item>
    <item>
      <title>Structured Logging in Go with log/slog: A Practical Guide</title>
      <link>https://webdevstation.com/posts/structured-logging-in-go-with-slog/</link>
      <pubDate>Tue, 04 Aug 2026 10:15:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/structured-logging-in-go-with-slog/</guid>
      <description>Replace fmt-style logging with Go&#39;s built-in log/slog package: JSON handlers, levels, context-aware loggers, grouped attributes and request-scoped fields, with…</description>
      <content:encoded><![CDATA[<p>For years, every Go service I wrote started with the same decision: which logging library this time? Since Go 1.21 that decision has a boring, excellent default — <code>log/slog</code> ships with the standard library, speaks JSON out of the box, and needs no dependency at all. This post is the tour I wish I had when I migrated my first service to it.</p>
<h2 id="why-structured-logs-beat-formatted-strings">Why Structured Logs Beat Formatted Strings</h2>
<p>Here is a line the old <code>log</code> package might produce:</p>
<pre tabindex="0"><code>2026/08/04 10:15:02 user 42 checkout failed after 1.2s: payment declined
</code></pre><p>It reads fine. Now try answering &ldquo;how many checkouts failed for users on the EU cluster last Tuesday between 14:00 and 15:00?&rdquo; You are writing a regular expression.</p>
<p>The same event as structured data:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{<span style="color:#f92672">&#34;time&#34;</span>:<span style="color:#e6db74">&#34;2026-08-04T10:15:02.113Z&#34;</span>,<span style="color:#f92672">&#34;level&#34;</span>:<span style="color:#e6db74">&#34;ERROR&#34;</span>,<span style="color:#f92672">&#34;msg&#34;</span>:<span style="color:#e6db74">&#34;checkout failed&#34;</span>,
</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;user_id&#34;</span>:<span style="color:#ae81ff">42</span>,<span style="color:#f92672">&#34;duration_ms&#34;</span>:<span style="color:#ae81ff">1204</span>,<span style="color:#f92672">&#34;reason&#34;</span>:<span style="color:#e6db74">&#34;payment declined&#34;</span>,<span style="color:#f92672">&#34;cluster&#34;</span>:<span style="color:#e6db74">&#34;eu&#34;</span>}
</span></span></code></pre></div><p>Now it is a query. Every log aggregator — Loki, Elasticsearch, CloudWatch, BigQuery — indexes those fields directly. The point of structured logging is not prettier output; it is that your logs become a queryable dataset.</p>
<h2 id="the-smallest-useful-setup">The Smallest Useful Setup</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;log/slog&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">logger</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">NewJSONHandler</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">HandlerOptions</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Level</span>: <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelInfo</span>,
</span></span><span style="display:flex;"><span>    }))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Make it the package-level default so slog.Info et al. use it too.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">SetDefault</span>(<span style="color:#a6e22e">logger</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;service started&#34;</span>, <span style="color:#e6db74">&#34;port&#34;</span>, <span style="color:#ae81ff">8080</span>, <span style="color:#e6db74">&#34;env&#34;</span>, <span style="color:#e6db74">&#34;production&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{<span style="color:#f92672">&#34;time&#34;</span>:<span style="color:#e6db74">&#34;2026-08-04T10:15:02.09Z&#34;</span>,<span style="color:#f92672">&#34;level&#34;</span>:<span style="color:#e6db74">&#34;INFO&#34;</span>,<span style="color:#f92672">&#34;msg&#34;</span>:<span style="color:#e6db74">&#34;service started&#34;</span>,<span style="color:#f92672">&#34;port&#34;</span>:<span style="color:#ae81ff">8080</span>,<span style="color:#f92672">&#34;env&#34;</span>:<span style="color:#e6db74">&#34;production&#34;</span>}
</span></span></code></pre></div><p>The variadic arguments are alternating keys and values. If you prefer something the compiler can check, use typed attributes instead:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;service started&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;port&#34;</span>, <span style="color:#ae81ff">8080</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;env&#34;</span>, <span style="color:#e6db74">&#34;production&#34;</span>),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Both forms are fine, and you can mix them. The typed form costs a little more typing and saves you from the classic odd-number-of-arguments bug, where a stray value ends up under the key <code>!BADKEY</code>.</p>
<p>For local development, swap the handler and keep everything else:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">handler</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Handler</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;ENV&#34;</span>) <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;development&#34;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">handler</span> = <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">NewTextHandler</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">HandlerOptions</span>{<span style="color:#a6e22e">Level</span>: <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelDebug</span>})
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">handler</span> = <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">NewJSONHandler</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">HandlerOptions</span>{<span style="color:#a6e22e">Level</span>: <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelInfo</span>})
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">SetDefault</span>(<span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">handler</span>))
</span></span></code></pre></div><h2 id="levels-and-changing-them-without-a-redeploy">Levels, and Changing Them Without a Redeploy</h2>
<p><code>slog</code> has four built-in levels — <code>Debug</code> (-4), <code>Info</code> (0), <code>Warn</code> (4), <code>Error</code> (8) — as plain integers, so you can define your own in between if you really need to.</p>
<p>The more useful trick is a <code>LevelVar</code>, which lets you change the level at runtime:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">logLevel</span> = new(<span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelVar</span>) <span style="color:#75715e">// defaults to Info</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">SetDefault</span>(<span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">NewJSONHandler</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">HandlerOptions</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Level</span>: <span style="color:#a6e22e">logLevel</span>,
</span></span><span style="display:flex;"><span>    })))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Flip to debug on demand — from a signal handler, an admin endpoint,</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// or a config watcher.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/debug/level&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">l</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Level</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">l</span>.<span style="color:#a6e22e">UnmarshalText</span>([]byte(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">FormValue</span>(<span style="color:#e6db74">&#34;level&#34;</span>))); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;bad level&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">logLevel</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">l</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Warn</span>(<span style="color:#e6db74">&#34;log level changed&#34;</span>, <span style="color:#e6db74">&#34;level&#34;</span>, <span style="color:#a6e22e">l</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Being able to turn on debug logging for two minutes on a misbehaving pod, without a deploy, has saved me more time than any other logging feature. Put that endpoint behind authentication — the <a href="/posts/how-to-control-router-access-permissions-in-go-web-apps/">router access permission patterns</a> I wrote about earlier work well for exactly this kind of internal route.</p>
<h2 id="attaching-context-with-with">Attaching Context With With</h2>
<p><code>logger.With</code> returns a new logger that carries the given attributes on every subsequent call. This is how you stop repeating yourself:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Once, at construction:</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Worker</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">log</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Logger</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewWorker</span>(<span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int</span>, <span style="color:#a6e22e">queue</span> <span style="color:#66d9ef">string</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">Worker</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Worker</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">log</span>: <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Default</span>().<span style="color:#a6e22e">With</span>(<span style="color:#e6db74">&#34;worker_id&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#e6db74">&#34;queue&#34;</span>, <span style="color:#a6e22e">queue</span>),
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">w</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Worker</span>) <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">job</span> <span style="color:#a6e22e">Job</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Every line from this worker carries worker_id and queue automatically.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;job started&#34;</span>, <span style="color:#e6db74">&#34;job_id&#34;</span>, <span style="color:#a6e22e">job</span>.<span style="color:#a6e22e">ID</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;job finished&#34;</span>, <span style="color:#e6db74">&#34;job_id&#34;</span>, <span style="color:#a6e22e">job</span>.<span style="color:#a6e22e">ID</span>, <span style="color:#e6db74">&#34;duration_ms&#34;</span>, <span style="color:#ae81ff">42</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>With</code> does the attribute formatting work once, at call time, rather than on every log line — so a long-lived logger built with <code>With</code> is cheaper than passing the same attributes repeatedly.</p>
<p>Use <code>WithGroup</code> when you want to namespace a block of fields:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">log</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Default</span>().<span style="color:#a6e22e">WithGroup</span>(<span style="color:#e6db74">&#34;http&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;request&#34;</span>, <span style="color:#e6db74">&#34;method&#34;</span>, <span style="color:#e6db74">&#34;GET&#34;</span>, <span style="color:#e6db74">&#34;path&#34;</span>, <span style="color:#e6db74">&#34;/users&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// {&#34;level&#34;:&#34;INFO&#34;,&#34;msg&#34;:&#34;request&#34;,&#34;http&#34;:{&#34;method&#34;:&#34;GET&#34;,&#34;path&#34;:&#34;/users&#34;}}</span>
</span></span></code></pre></div><p>Or group inline, for one call:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;upstream call&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;service&#34;</span>, <span style="color:#e6db74">&#34;billing&#34;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Group</span>(<span style="color:#e6db74">&#34;response&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;status&#34;</span>, <span style="color:#ae81ff">502</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Duration</span>(<span style="color:#e6db74">&#34;latency&#34;</span>, <span style="color:#ae81ff">1200</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>),
</span></span><span style="display:flex;"><span>    ),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h2 id="request-scoped-logging">Request-Scoped Logging</h2>
<p>The pattern that pays off most in a web service: put a logger carrying the request ID into the request context, then let every layer below pull it out. If you have not used <code>context.Context</code> for this kind of request-scoped data before, <a href="/posts/understanding-golang-context/">understanding Golang context</a> covers the rules — including why the key must be an unexported type.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">logging</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;log/slog&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">ctxKey</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Into returns a copy of ctx carrying logger.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Into</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">logger</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Logger</span>) <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithValue</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">ctxKey</span>{}, <span style="color:#a6e22e">logger</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// From returns the logger stored in ctx, or the default logger.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">From</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Logger</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">l</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">ctxKey</span>{}).(<span style="color:#f92672">*</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Logger</span>); <span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">l</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Default</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The middleware that fills it in — a close cousin of the handler-wrapping in my <a href="/posts/go-middleware-example/">Go middleware example</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Middleware</span>(<span style="color:#a6e22e">next</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">requestID</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Header</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;X-Request-Id&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">requestID</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">requestID</span> = <span style="color:#a6e22e">uuid</span>.<span style="color:#a6e22e">NewString</span>()
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">log</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Default</span>().<span style="color:#a6e22e">With</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;request_id&#34;</span>, <span style="color:#a6e22e">requestID</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;method&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Method</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;path&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Path</span>,
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">start</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">rec</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">statusRecorder</span>{<span style="color:#a6e22e">ResponseWriter</span>: <span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">status</span>: <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">next</span>.<span style="color:#a6e22e">ServeHTTP</span>(<span style="color:#a6e22e">rec</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">WithContext</span>(<span style="color:#a6e22e">logging</span>.<span style="color:#a6e22e">Into</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">log</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;request completed&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;status&#34;</span>, <span style="color:#a6e22e">rec</span>.<span style="color:#a6e22e">status</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;duration_ms&#34;</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Since</span>(<span style="color:#a6e22e">start</span>).<span style="color:#a6e22e">Milliseconds</span>(),
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// statusRecorder remembers the status code written by the handler.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">statusRecorder</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">status</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">statusRecorder</span>) <span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">code</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">status</span> = <span style="color:#a6e22e">code</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">ResponseWriter</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">code</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now any function that already takes a <code>context.Context</code> can log with full request context and no extra parameters:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int64</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">logging</span>.<span style="color:#a6e22e">From</span>(<span style="color:#a6e22e">ctx</span>).<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;loading user&#34;</span>, <span style="color:#e6db74">&#34;user_id&#34;</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Every line from that request — across every layer — shares a <code>request_id</code>. Tracing one user&rsquo;s bad afternoon becomes a single filter in your log viewer.</p>
<h2 id="redacting-secrets-with-replaceattr">Redacting Secrets With ReplaceAttr</h2>
<p><code>HandlerOptions.ReplaceAttr</code> runs on every attribute before it is written. It is the right place to enforce policy centrally instead of trusting each call site.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">sensitive</span> = <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">bool</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;password&#34;</span>: <span style="color:#66d9ef">true</span>, <span style="color:#e6db74">&#34;token&#34;</span>: <span style="color:#66d9ef">true</span>, <span style="color:#e6db74">&#34;authorization&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;api_key&#34;</span>: <span style="color:#66d9ef">true</span>, <span style="color:#e6db74">&#34;secret&#34;</span>: <span style="color:#66d9ef">true</span>, <span style="color:#e6db74">&#34;set-cookie&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">handler</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">NewJSONHandler</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">HandlerOptions</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Level</span>:     <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelInfo</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">AddSource</span>: <span style="color:#66d9ef">true</span>, <span style="color:#75715e">// include file:line — useful for errors</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ReplaceAttr</span>: <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">groups</span> []<span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">a</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Attr</span>) <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Attr</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">sensitive</span>[<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">ToLower</span>(<span style="color:#a6e22e">a</span>.<span style="color:#a6e22e">Key</span>)] {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">String</span>(<span style="color:#a6e22e">a</span>.<span style="color:#a6e22e">Key</span>, <span style="color:#e6db74">&#34;[REDACTED]&#34;</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Rename &#34;time&#34; to &#34;timestamp&#34; to match the rest of our pipeline.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">a</span>.<span style="color:#a6e22e">Key</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">TimeKey</span> <span style="color:#f92672">&amp;&amp;</span> len(<span style="color:#a6e22e">groups</span>) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">a</span>.<span style="color:#a6e22e">Key</span> = <span style="color:#e6db74">&#34;timestamp&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">a</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>For types you control, implementing <code>LogValuer</code> is even better — the value redacts itself wherever it is logged:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Password</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// LogValue implements slog.LogValuer so a Password never reaches a log sink.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">Password</span>) <span style="color:#a6e22e">LogValue</span>() <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Value</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">StringValue</span>(<span style="color:#e6db74">&#34;[REDACTED]&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Info</span>(<span style="color:#e6db74">&#34;login attempt&#34;</span>, <span style="color:#e6db74">&#34;user&#34;</span>, <span style="color:#e6db74">&#34;alex&#34;</span>, <span style="color:#e6db74">&#34;password&#34;</span>, <span style="color:#a6e22e">Password</span>(<span style="color:#e6db74">&#34;hunter2&#34;</span>))
</span></span><span style="display:flex;"><span><span style="color:#75715e">// {&#34;level&#34;:&#34;INFO&#34;,&#34;msg&#34;:&#34;login attempt&#34;,&#34;user&#34;:&#34;alex&#34;,&#34;password&#34;:&#34;[REDACTED]&#34;}</span>
</span></span></code></pre></div><p><code>LogValuer</code> is also how you log a struct compactly. Give your <code>User</code> type a <code>LogValue</code> that returns a group with just the ID and role, and you never accidentally dump an entire record — with its email address and hashed password — into a log line.</p>
<h2 id="logging-errors-properly">Logging Errors Properly</h2>
<p>Log the error value, not a formatted string, and let the handler decide how to render it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;checkout failed&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>, <span style="color:#e6db74">&#34;user_id&#34;</span>, <span style="color:#a6e22e">userID</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;checkout: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Two habits worth keeping:</p>
<ul>
<li><strong>Log once, at the boundary.</strong> If you log here <em>and</em> return the error, every caller up the stack logs it again. That is the same rule I covered in <a href="/posts/error-handling-in-go/">error handling in Go</a>, and <code>slog</code> does not change it.</li>
<li><strong>Do not log <code>context.Canceled</code> as an error.</strong> A user closing a tab is not an incident.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> {
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Canceled</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;client disconnected&#34;</span>, <span style="color:#e6db74">&#34;path&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Path</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;request failed&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>, <span style="color:#e6db74">&#34;path&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Path</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="bridging-libraries-that-use-the-old-log-package">Bridging Libraries That Use the Old log Package</h2>
<p>Dependencies still writing to the standard <code>log</code> package do not have to break your JSON output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Route everything from the standard logger through slog at Info level.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">SetLogLoggerLevel</span>(<span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelInfo</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Or hand a specific component its own *log.Logger backed by slog.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Addr</span>:     <span style="color:#e6db74">&#34;:8080&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Handler</span>:  <span style="color:#a6e22e">mux</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrorLog</span>: <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">NewLogLogger</span>(<span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Default</span>().<span style="color:#a6e22e">Handler</span>(), <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelError</span>),
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That second line is worth adding to every server you write — <code>http.Server</code> logs connection errors through <code>ErrorLog</code>, and by default they land on stderr as unstructured text that your aggregator will not parse.</p>
<h2 id="performance-notes">Performance Notes</h2>
<p><code>slog</code> is designed so the common path allocates very little, but a few habits matter:</p>
<ul>
<li>
<p>Guard genuinely expensive debug work behind <code>Enabled</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Default</span>().<span style="color:#a6e22e">Enabled</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelDebug</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Debug</span>(<span style="color:#e6db74">&#34;payload&#34;</span>, <span style="color:#e6db74">&#34;body&#34;</span>, <span style="color:#a6e22e">expensiveDump</span>(<span style="color:#a6e22e">req</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></li>
<li>
<p>Prefer <code>LogAttrs</code> in hot paths — it takes typed attributes and skips the <code>any</code> boxing:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LogAttrs</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">LevelInfo</span>, <span style="color:#e6db74">&#34;cache hit&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;key&#34;</span>, <span style="color:#a6e22e">key</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#e6db74">&#34;size&#34;</span>, len(<span style="color:#a6e22e">val</span>)),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div></li>
<li>
<p>Build the per-request logger once with <code>With</code>, not per log line.</p>
</li>
</ul>
<p>None of this matters at ten requests per second. All of it matters in a hot loop — the same way <a href="/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/">caching with Ristretto</a> only pays off once you are actually calling the expensive thing often.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li>One handler, configured once in <code>main</code>, installed with <code>slog.SetDefault</code>.</li>
<li>JSON in production, text locally.</li>
<li>A <code>LevelVar</code> so you can raise verbosity without a redeploy.</li>
<li>A request-scoped logger in the context, carrying the request ID.</li>
<li><code>ReplaceAttr</code> or <code>LogValuer</code> for anything secret.</li>
<li>Errors logged once, at the boundary, as values.</li>
<li><code>http.Server.ErrorLog</code> wired through <code>slog</code>.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p><code>log/slog</code> removed the last dependency I used to add reflexively to every new Go service. It is fast enough, it is in the standard library, and its handler interface means you can change output format or destination without touching a single call site. If you are still logging with <code>fmt.Sprintf</code>, the migration is mostly mechanical — and the first time you filter a week of logs by <code>request_id</code>, you will not want to go back.</p>
<p>It pays off especially well for anything token-billed, where a handful of numeric fields per call is the only record of what a feature costs — <a href="/posts/calling-claude-from-go/">calling an LLM from Go</a> covers which ones to log.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
    </item>
    <item>
      <title>Error Handling in Go: Wrapping, Sentinel Errors and errors.Is/As</title>
      <link>https://webdevstation.com/posts/error-handling-in-go/</link>
      <pubDate>Tue, 21 Jul 2026 09:30:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/error-handling-in-go/</guid>
      <description>A practical guide to Go error handling — wrapping with %w, sentinel errors, custom error types, errors.Is and errors.As, and where each one belongs in a real…</description>
      <content:encoded><![CDATA[<p>Every Go codebase I have joined had the same weak spot: errors. Not the <code>if err != nil</code> part — everybody writes that — but everything after it. Errors get logged three times on the way up, lose their context somewhere in the middle, or get compared with <code>err.Error() == &quot;not found&quot;</code>. In this post I want to share the small set of rules I now apply everywhere, and the standard library features that make them work.</p>
<h2 id="the-one-rule-that-fixes-most-of-it">The One Rule That Fixes Most of It</h2>
<p>Handle an error <strong>once</strong>. Everywhere else, add context and pass it up.</p>
<p>&ldquo;Handling&rdquo; means doing something a caller cannot: returning a 404, retrying, falling back to a default, logging and moving on. If you are not doing one of those, you are just a link in the chain — and a link&rsquo;s only job is to explain where it sits.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Bad: the error is logged here and returned, so it will be logged again</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// by every caller on the way up.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int64</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">query</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;failed to load user: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">u</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Good: add what this layer knows, and let the caller decide.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int64</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">query</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;load user %d: %w&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">u</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The second version produces messages that read like a stack trace written in English:</p>
<pre tabindex="0"><code>handle GET /users/42: load user 42: query users: sql: no rows in result set
</code></pre><h2 id="wrapping-with-w">Wrapping With %w</h2>
<p><code>fmt.Errorf</code> has a special verb, <code>%w</code>, that wraps the original error instead of flattening it to a string. The result behaves like a normal error, but the original stays reachable underneath.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#e6db74">&#34;/etc/app/config.yaml&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">wrapped</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;read config: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">wrapped</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// read config: open /etc/app/config.yaml: no such file or directory</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">wrapped</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">ErrNotExist</span>)) <span style="color:#75715e">// true</span>
</span></span></code></pre></div><p>That last line is the whole point. <code>%v</code> would have given you the same message, but <code>errors.Is</code> would have returned <code>false</code> because the chain was broken.</p>
<p>A few conventions that keep the output readable:</p>
<ul>
<li>Start the message with a lowercase verb phrase describing what <em>this</em> function was doing: <code>&quot;load user&quot;</code>, <code>&quot;encode response&quot;</code>, <code>&quot;dial redis&quot;</code>.</li>
<li>Do not end with punctuation, and do not include the word &ldquo;error&rdquo; or &ldquo;failed&rdquo; — the chain already reads as a failure.</li>
<li>Put the wrapped error last, after a colon and a space.</li>
</ul>
<p>Since Go 1.20 you can wrap more than one error in a single call, which is handy when you are cleaning up:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">doWork</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">cerr</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">Close</span>(); <span style="color:#a6e22e">cerr</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;work failed: %w; close failed: %w&#34;</span>, <span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">cerr</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="sentinel-errors-for-conditions-callers-branch-on">Sentinel Errors: For Conditions Callers Branch On</h2>
<p>A sentinel is a package-level error value that callers are meant to recognise.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">store</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrNotFound</span>  = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;not found&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrConflict</span>  = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;conflict&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrForbidden</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;forbidden&#34;</span>)
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Return them wrapped, so the caller gets both the identity and the context:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int64</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">query</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">sql</span>.<span style="color:#a6e22e">ErrNoRows</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;load user %d: %w&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">ErrNotFound</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;load user %d: %w&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">u</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And check them with <code>errors.Is</code>, which walks the whole chain:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> {
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrNotFound</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;user not found&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;load user&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;internal error&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Never compare with <code>==</code> when the error might be wrapped, and never compare error strings. <code>errors.Is(err, ErrNotFound)</code> keeps working when someone adds another wrapping layer three months from now; <code>err == ErrNotFound</code> silently stops working.</p>
<p>Keep the list short. Sentinels are part of your package&rsquo;s public API — every one you export is a promise. If callers cannot meaningfully branch on it, it should not be a sentinel.</p>
<h2 id="custom-error-types-when-you-need-to-carry-data">Custom Error Types: When You Need to Carry Data</h2>
<p>A sentinel says <em>what went wrong</em>. A custom type also says <em>with what</em>. Reach for one when the caller needs a field, not just an identity.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// ValidationError reports a single field that failed validation.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">ValidationError</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Field</span>  <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Reason</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;field %q is invalid: %s&#34;</span>, <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Field</span>, <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Reason</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>errors.As</code> finds it anywhere in the chain and assigns it to your variable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">vErr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">vErr</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewEncoder</span>(<span style="color:#a6e22e">w</span>).<span style="color:#a6e22e">Encode</span>(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;field&#34;</span>:  <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Field</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;reason&#34;</span>: <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Reason</span>,
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Two details that trip people up:</p>
<ol>
<li><strong>Pass a pointer to the target.</strong> <code>errors.As(err, &amp;vErr)</code> where <code>vErr</code> is already a <code>*ValidationError</code>. Passing <code>vErr</code> directly panics.</li>
<li><strong>Be consistent about pointer vs. value receivers.</strong> If <code>Error()</code> is defined on <code>*ValidationError</code>, then only <code>*ValidationError</code> implements <code>error</code>. Return <code>&amp;ValidationError{...}</code>, and match against <code>*ValidationError</code>.</li>
</ol>
<p>If your type wraps another error, give it an <code>Unwrap</code> method so <code>errors.Is</code> can keep walking:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">QueryError</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Query</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Err</span>   <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">QueryError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> { <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Query</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;: &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Err</span>.<span style="color:#a6e22e">Error</span>() }
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">QueryError</span>) <span style="color:#a6e22e">Unwrap</span>() <span style="color:#66d9ef">error</span> { <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Err</span> }
</span></span></code></pre></div><p>Now <code>errors.Is(err, sql.ErrNoRows)</code> still works even with a <code>QueryError</code> in the middle.</p>
<h2 id="collecting-several-errors-with-errorsjoin">Collecting Several Errors With errors.Join</h2>
<p>When you validate a whole struct, failing on the first problem makes for a frustrating API. <code>errors.Join</code> (Go 1.20) combines errors into one value that <code>errors.Is</code> and <code>errors.As</code> can still search.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#a6e22e">CreateUserRequest</span>) <span style="color:#a6e22e">Validate</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">errs</span> []<span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Email</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errs</span> = append(<span style="color:#a6e22e">errs</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ValidationError</span>{<span style="color:#a6e22e">Field</span>: <span style="color:#e6db74">&#34;email&#34;</span>, <span style="color:#a6e22e">Reason</span>: <span style="color:#e6db74">&#34;required&#34;</span>})
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Password</span>) &lt; <span style="color:#ae81ff">12</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errs</span> = append(<span style="color:#a6e22e">errs</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ValidationError</span>{<span style="color:#a6e22e">Field</span>: <span style="color:#e6db74">&#34;password&#34;</span>, <span style="color:#a6e22e">Reason</span>: <span style="color:#e6db74">&#34;too short&#34;</span>})
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Join returns nil when every element is nil, so this is safe as-is.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#a6e22e">errs</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The joined error prints one message per line, and <code>errors.As</code> will find the first <code>*ValidationError</code> inside it.</p>
<h2 id="which-tool-for-which-job">Which Tool for Which Job</h2>
<table>
	<thead>
			<tr>
					<th>Situation</th>
					<th>Reach for</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Adding context on the way up</td>
					<td><code>fmt.Errorf(&quot;...: %w&quot;, err)</code></td>
			</tr>
			<tr>
					<td>Caller branches on a known condition</td>
					<td>Sentinel + <code>errors.Is</code></td>
			</tr>
			<tr>
					<td>Caller needs data about the failure</td>
					<td>Custom type + <code>errors.As</code></td>
			</tr>
			<tr>
					<td>Several independent failures at once</td>
					<td><code>errors.Join</code></td>
			</tr>
			<tr>
					<td>Failure is expected and unremarkable</td>
					<td>Return a plain value, not an error</td>
			</tr>
	</tbody>
</table>
<p>That last row matters more than it looks. A cache miss is not an error. An empty search result is not an error. Reserve errors for situations where the caller genuinely cannot continue as planned.</p>
<h2 id="errors-at-the-http-boundary">Errors at the HTTP Boundary</h2>
<p>The boundary is where errors finally get handled, and it is worth doing that in exactly one place. Middleware is a natural home for it — the same pattern as the <a href="/posts/go-middleware-example/">Go middleware example</a>, just applied to errors instead of responses.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// handlerFunc is like http.HandlerFunc but may return an error.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">handlerFunc</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// wrap turns a handlerFunc into a plain http.Handler, translating</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// errors into status codes in one place.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">wrap</span>(<span style="color:#a6e22e">h</span> <span style="color:#a6e22e">handlerFunc</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">h</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">vErr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">switch</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrNotFound</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;not found&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrForbidden</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;forbidden&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusForbidden</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">vErr</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Error</span>(), <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Canceled</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// The client hung up. Nothing to report.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Log the full chain, tell the client nothing.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;request failed&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;method&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Method</span>, <span style="color:#e6db74">&#34;path&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Path</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;internal error&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Handlers become quiet and linear:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">getUser</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">ParseInt</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">PathValue</span>(<span style="color:#e6db74">&#34;id&#34;</span>), <span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">64</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ValidationError</span>{<span style="color:#a6e22e">Field</span>: <span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#a6e22e">Reason</span>: <span style="color:#e6db74">&#34;must be an integer&#34;</span>}
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;get user: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewEncoder</span>(<span style="color:#a6e22e">w</span>).<span style="color:#a6e22e">Encode</span>(<span style="color:#a6e22e">u</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>context.Canceled</code> case is easy to forget and shows up constantly in production: a user closes the tab, the request context is cancelled, and your dashboard fills with 500s that nothing went wrong for. If you have not met that machinery yet, <a href="/posts/understanding-golang-context/">understanding Golang context</a> covers where those cancellations come from.</p>
<h2 id="common-pitfalls">Common Pitfalls</h2>
<table>
	<thead>
			<tr>
					<th>Pitfall</th>
					<th>What to do instead</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>if err.Error() == &quot;not found&quot;</code></td>
					<td><code>errors.Is(err, ErrNotFound)</code></td>
			</tr>
			<tr>
					<td><code>%v</code> when the caller needs the chain</td>
					<td><code>%w</code></td>
			</tr>
			<tr>
					<td>Logging <em>and</em> returning the same error</td>
					<td>Return it; log once at the boundary</td>
			</tr>
			<tr>
					<td><code>errors.As(err, vErr)</code></td>
					<td><code>errors.As(err, &amp;vErr)</code> — pass a pointer</td>
			</tr>
			<tr>
					<td>Wrapping with the caller&rsquo;s own function name</td>
					<td>Describe the <em>operation</em>, not the function</td>
			</tr>
			<tr>
					<td>Exporting a sentinel nobody branches on</td>
					<td>Keep it unexported, or drop it</td>
			</tr>
	</tbody>
</table>
<p>One more, subtle enough to deserve its own note: <strong>a non-nil interface holding a nil pointer is not nil.</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">find</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span> { <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">check</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">find</span>() <span style="color:#75715e">// returns a non-nil error holding a nil *ValidationError</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">check</span>() <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span>) <span style="color:#75715e">// false — almost certainly not what you wanted</span>
</span></span></code></pre></div><p>Declare the return type as <code>error</code> and return a literal <code>nil</code>, or check the concrete value before returning it.</p>
<h2 id="testing-error-paths">Testing Error Paths</h2>
<p>Assert on identity and type, never on the message text. Messages are for humans and will change.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestUserNotFound</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">999</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrNotFound</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;got %v, want ErrNotFound&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestValidation</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">CreateUserRequest</span>{}.<span style="color:#a6e22e">Validate</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">vErr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">vErr</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;got %v, want *ValidationError&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Field</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;email&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;got field %q, want email&#34;</span>, <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Field</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This pairs nicely with mocking the database layer, which I covered in <a href="/posts/how-to-test-database-interactions-go/">how to test database interactions in Golang applications</a> — you can force <code>sql.ErrNoRows</code> and check that your store translates it into <code>ErrNotFound</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Go&rsquo;s error handling is verbose, but it is also unusually honest: every failure is a value you can inspect, wrap and route. Wrap with <code>%w</code> on the way up, export a small set of sentinels for the conditions callers care about, use custom types when they need the details, and handle everything exactly once at the boundary. Do that and the <code>if err != nil</code> blocks stop feeling like noise and start reading like documentation.</p>
<p>One case that breaks the usual rules and deserves a look: an LLM call can return HTTP 200 while declining to answer, so <code>err</code> is nil and there is nothing in the content. <a href="/posts/calling-claude-from-go/">Calling an LLM from Go</a> covers that branch.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
      <category>Testing</category>
    </item>
    <item>
      <title>Understanding Golang Context: Cancellation, Timeouts, and Deadlines</title>
      <link>https://webdevstation.com/posts/understanding-golang-context/</link>
      <pubDate>Mon, 16 Jun 2025 14:01:13 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/understanding-golang-context/</guid>
      <description>Deep-dive into Golang&#39;s context package to manage cancellation, timeouts, deadlines, and request-scoped data across goroutines with practical examples and best…</description>
      <content:encoded><![CDATA[<p>When working with concurrent operations in Go, few topics are as important—and as misunderstood—as the <strong><code>context</code></strong> package. Whether you are building an HTTP API, orchestrating background workers, or integrating with external services, <em>golang context</em> is the idiomatic way to propagate cancellation signals, enforce timeouts, carry deadlines, and pass request-scoped values.</p>
<p>In this article we will demystify the <code>context</code> package, walk through common use-cases, and share production-tested best practices.</p>
<h2 id="why-context-exists">Why Context Exists</h2>
<p>Go’s lightweight goroutines make it trivial to spin up concurrent work, but once you have hundreds (or thousands) of goroutines you need a structured way to:</p>
<ol>
<li>Cancel unfinished work when a client disconnects or a parent task ends.</li>
<li>Enforce upper time bounds to prevent runaway operations.</li>
<li>Propagate deadlines deep into the call graph.</li>
<li>Attach request-level metadata (trace IDs, auth tokens, etc.) without polluting function signatures.</li>
</ol>
<p>The <code>context</code> package solves these problems with two core ideas:</p>
<ul>
<li><strong>Cancellation propagation</strong> via <code>Done()</code> channels.</li>
<li><strong>Immutable trees</strong>—each derived context references its parent, forming a hierarchy that can be cancelled from the root.</li>
</ul>
<h2 id="creating-and-cancelling-contexts">Creating and Cancelling Contexts</h2>
<p>The building blocks are <code>context.Background()</code> (or <code>context.TODO()</code>), <code>context.WithCancel</code>, <code>context.WithTimeout</code>, and <code>context.WithDeadline</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Start with a root context</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithCancel</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>())
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>() <span style="color:#75715e">// always release resources</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Fire off a worker goroutine</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;worker cancelled:&#34;</span>, <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Err</span>())
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Simulate some condition that requires cancellation</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">500</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">100</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Running this prints:</p>
<pre tabindex="0"><code>worker cancelled: context canceled
</code></pre><h3 id="timeout-helper">Timeout Helper</h3>
<p><code>context.WithTimeout</code> wraps <code>WithCancel</code> plus a timer:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">parent</span>, <span style="color:#ae81ff">2</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// After 2s: ctx.Err() == context.DeadlineExceeded</span>
</span></span></code></pre></div><p>Remember to <strong>always call the returned <code>cancel</code></strong>—even when the timeout expires—so the timer’s internal resources are freed.</p>
<h2 id="passing-context-down-the-call-stack">Passing Context Down the Call Stack</h2>
<p>The first parameter of every context-aware function should be <code>ctx context.Context</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">url</span> <span style="color:#66d9ef">string</span>) ([]<span style="color:#66d9ef">byte</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewRequestWithContext</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>, <span style="color:#a6e22e">url</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">resp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">DefaultClient</span>.<span style="color:#a6e22e">Do</span>(<span style="color:#a6e22e">req</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">Body</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">ReadAll</span>(<span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">Body</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>When <code>ctx</code> is cancelled upstream, <code>http.Client</code> aborts the request automatically.</p>
<h2 id="deadlines-vs-timeouts">Deadlines vs. Timeouts</h2>
<p>A <strong>deadline</strong> is an absolute moment (<code>2025-06-16T14:05:00+02:00</code>) while a <strong>timeout</strong> is a relative duration (<code>5s</code>). Internally both are implemented with <code>WithDeadline</code>, but modelling them correctly communicates intent:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">deadline</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithDeadline</span>(<span style="color:#a6e22e">parent</span>, <span style="color:#a6e22e">deadline</span>)
</span></span></code></pre></div><h2 id="storing-values-in-context">Storing Values in Context</h2>
<p><code>context.WithValue</code> allows passing request-scoped data without modifying every function signature. Use it sparingly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Key type prevents collisions</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">type</span> <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">traceIDKey</span> <span style="color:#a6e22e">key</span> = <span style="color:#e6db74">&#34;traceID&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">ctx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithValue</span>(<span style="color:#a6e22e">parent</span>, <span style="color:#a6e22e">traceIDKey</span>, <span style="color:#e6db74">&#34;abc-123&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// Downstream retrieval</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Value</span>(<span style="color:#a6e22e">traceIDKey</span>); <span style="color:#a6e22e">v</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>     <span style="color:#a6e22e">traceID</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">v</span>.(<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span>     <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;traceID:&#34;</span>, <span style="color:#a6e22e">traceID</span>)
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><h3 id="guidelines">Guidelines</h3>
<ol>
<li>Only store immutable, request-specific data (IDs, auth tokens).</li>
<li>Never store optional params that belong in function arguments.</li>
<li>Define unexported key types to avoid collisions across packages.</li>
</ol>
<h2 id="common-pitfalls">Common Pitfalls</h2>
<table>
	<thead>
			<tr>
					<th>Pitfall</th>
					<th>Solution</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Returning <code>nil</code> context</td>
					<td>Accept a <code>context.Context</code> argument and demand callers pass one.</td>
			</tr>
			<tr>
					<td>Forgetting to cancel</td>
					<td>Always <code>defer cancel()</code> after <code>WithCancel / WithTimeout / WithDeadline</code>.</td>
			</tr>
			<tr>
					<td>Blocking select without <code>&lt;-ctx.Done()</code></td>
					<td>Include cancellation in every <code>select</code> that may block.</td>
			</tr>
			<tr>
					<td>Misusing <code>WithValue</code> for configs</td>
					<td>Pass explicit parameters instead.</td>
			</tr>
	</tbody>
</table>
<h2 id="end-to-end-example-http-server-with-timeouts">End-to-End Example: HTTP Server with Timeouts</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">srv</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Server</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Addr</span>:         <span style="color:#e6db74">&#34;:8080&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ReadTimeout</span>:  <span style="color:#ae81ff">3</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">WriteTimeout</span>: <span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Handler</span>:      <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#a6e22e">handler</span>),
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Shutdown gracefully on interrupt</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">After</span>(<span style="color:#ae81ff">10</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>) <span style="color:#75715e">// Simulate interrupt</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithTimeout</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">3</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">Shutdown</span>(<span style="color:#a6e22e">ctx</span>)
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Serving on :8080&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">srv</span>.<span style="color:#a6e22e">ListenAndServe</span>(); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span> {
</span></span><span style="display:flex;"><span>        panic(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Server gracefully stopped&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// r.Context() inherits deadlines from the server</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">After</span>(<span style="color:#ae81ff">2</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>):
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#e6db74">&#34;done&#34;</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>().<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;request cancelled&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusRequestTimeout</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="best-practices-checklist">Best Practices Checklist</h2>
<ul>
<li>Pass <code>context.Context</code> as the first parameter; never embed it in structs.</li>
<li>Do not store contexts—pass them along the call chain.</li>
<li>Cancel contexts to free resources early.</li>
<li>Use short-lived timeouts close to I/O boundaries rather than a single large timeout at the root.</li>
<li>Keep functions context-aware; return early on <code>&lt;-ctx.Done()</code>.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>The <em>golang context</em> package brings order to concurrent Go programs by standardising how we propagate cancellation and deadlines. Mastering it unlocks more reliable, resource-efficient applications.</p>
<p>Context shows up everywhere once you start looking for it. Three follow-ups that use it heavily: <a href="/posts/graceful-shutdown-in-go-web-services/">graceful shutdown in Go web services</a>, <a href="/posts/worker-pools-in-go-with-errgroup/">worker pools with errgroup</a>, and <a href="/posts/simple-queue-implementation-in-golang/">a simple queue implementation</a>. It also turns up in every LLM call you will ever write, since those are slow, cancellable and worth a deadline — see <a href="/posts/calling-claude-from-go/">calling an LLM from Go</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
    </item>
    <item>
      <title>103 Early Hints in Go, or the new Way of How to Improve Performance of a Web Page written in Go</title>
      <link>https://webdevstation.com/posts/early-hints-in-go-or-the-way-of-how-to-improve-perforamnce-of-web-page/</link>
      <pubDate>Mon, 14 Nov 2022 19:40:21 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/early-hints-in-go-or-the-way-of-how-to-improve-perforamnce-of-web-page/</guid>
      <description>Learn how to implement HTTP 103 Early Hints in Go 1.19+ to significantly improve web page loading performance by enabling browsers to preload resources while waiting…</description>
      <content:encoded><![CDATA[<p>Since Go 1.19 we can use a new <code>103 (Early Hints)</code> http status code when we create web applications. Let&rsquo;s figure out how and when this could help us.
We are going to create a simple golang web server that servers some html content. One html page will be served with <code>103</code> header and another one without.
After loading comparison we will see how early hints can improve page performance.</p>
<p>Early hints is a special HTTP header that is sent before the web server sends the final HTTP response to the client. At this moment it&rsquo;s supported only by Chrome browser.
As soon as the browser requests a page, server immediately returns 103 early hints header. In the meantime, a server will generate a usual HTTP response. This helps us utilize in maximum the loading time by letting browser know what resources it should preload while waiting for the final response from a server.</p>
<p>Enough theory, let&rsquo;s write some code :)</p>
<p>First, I&rsquo;m going to create an index.html with some dummy structure. Also, I will load <code>bootsrap</code> frontend framework to simulate some heavy css and js references during page load.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">title</span>&gt;Hello!&lt;/<span style="color:#f92672">title</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">link</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css&#34;</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;stylesheet&#34;</span> <span style="color:#a6e22e">integrity</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi&#34;</span> <span style="color:#a6e22e">crossorigin</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;anonymous&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;p-2 bg-success&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">h1</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;text-white&#34;</span>&gt;Hello!&lt;/<span style="color:#f92672">h1</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js&#34;</span> <span style="color:#a6e22e">integrity</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3&#34;</span> <span style="color:#a6e22e">crossorigin</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;anonymous&#34;</span>&gt;&lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>Now we need to serve it. Let&rsquo;s create a server.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//go:embed index.html</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">index</span> <span style="color:#66d9ef">string</span> <span style="color:#75715e">// embeded index.html</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Starting server...&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Handler for the page without early hints.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/1&#34;</span>, <span style="color:#a6e22e">noHintsHandler</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Handler for the page with early hints</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/2&#34;</span>, <span style="color:#a6e22e">withHintsHandler</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8082&#34;</span>, <span style="color:#66d9ef">nil</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">noHintsHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">template</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Parse</span>(<span style="color:#a6e22e">index</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Execute</span>(<span style="color:#a6e22e">w</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">withHintsHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Adding headers with preload information for bootstrap.min.css and bootstrap.bundle.min.js</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#e6db74">&#34;Link&#34;</span>, <span style="color:#e6db74">&#34;&lt;https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css&gt;; rel=preload; as=style&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#e6db74">&#34;Link&#34;</span>, <span style="color:#e6db74">&#34;&lt;https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js&gt;; rel=preload; as=script&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Sending 103 status code.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusEarlyHints</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">t</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">template</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Parse</span>(<span style="color:#a6e22e">index</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Sending 200 status code.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Execute</span>(<span style="color:#a6e22e">w</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now it&rsquo;s time to see our pages in action! Run our server <code>go run main.go</code>, open the page without early hints <code>http://localhost:8082/1</code> in Chrome,
open inspector, go to Lighthouse tab and click on &ldquo;Analyze page load&rdquo; button.
And this is what we can see:
<img src="/images/2022/1.png" alt="Chrome Lighthouse report for the Go page without early hints, showing a First Contentful Paint of 1492.8ms" title="Performance results for the page without early hints">
It takes a while until bootstrap resources got loaded by a browser. As result, FCP (First Contentful Paint) is <code>1492,8ms</code>.</p>
<p>Now, let&rsquo;s do the same for the page with the early hints <code>http://localhost:8082/2</code> And this is a result:
<img src="/images/2022/2.png" alt="Chrome Lighthouse report for the same page served with 103 Early Hints, showing a First Contentful Paint of 437.8ms" title="Performance results for the page with early hints">
As you can see, the page loaded much faster now. Bootstrap dependencies (bootstrap.min.css and bootstrap.bundle.min.js) were preloaded in the beginning and FCP now is <code>437,8ms</code>. More than 3 times faster, quite an impressive result!</p>
<p>However, it does not mean that you have to preload absolutely all resources now. Just try to experiment with these things, see how it affects your page performance and decide for yourself the right balance.</p>
<p>You can find the source code <a href="https://github.com/alexsergivan/blog-examples/tree/master/early-hints">here</a>.</p>
<p>If you want to measure the difference on your own service rather than take my numbers for it, <a href="/posts/an-easy-way-to-loadtest-your-web-apps/">an easy way to load test your web apps</a> shows the setup I use. And for the wins that happen before the request even reaches your handler, have a look at <a href="/posts/how-to-make-nginx-cookie-aware/">how to make Nginx cache cookie aware</a>.</p>]]></content:encoded>
      <category>Performance Optimization</category>
      <category>Web Development</category>
    </item>
    <item>
      <title>How to Show Flash Messages in Go web applications (with Echo framework)</title>
      <link>https://webdevstation.com/posts/how-to-show-flash-messages-in-go-echo/</link>
      <pubDate>Thu, 04 Feb 2021 17:28:00 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/how-to-show-flash-messages-in-go-echo/</guid>
      <description>Learn how to implement flash messages in Go web applications using Echo framework and Gorilla Sessions to improve user experience by providing feedback after form…</description>
      <content:encoded><![CDATA[<p>When we create a web application, usually, there a need to communicate with the users to inform them about the results
of their actions. The easiest way to communicate - is to send messages. These messages might be warnings, errors, or just
informational text. In this article, we will improve the UX of our user authentication application from the <a href="https://webdevstation.com/posts/user-authentication-with-go-using-jwt-token/">previous article</a>
by adding an error flash message when the user entered a wrong password and a success message after user authorisation.</p>
<p>We are going to use cookies to store messages in-between requests. To not reinvent the wheel, we will install <a href="https://github.com/gorilla/sessions">Gorilla Sessions</a>
package.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">go</span> <span style="color:#a6e22e">get</span> <span style="color:#a6e22e">github</span>.<span style="color:#a6e22e">com</span><span style="color:#f92672">/</span><span style="color:#a6e22e">gorilla</span><span style="color:#f92672">/</span><span style="color:#a6e22e">sessions</span>
</span></span></code></pre></div><p>Next, let&rsquo;s create a <code>messages</code> package (messages/messages.go):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">messages</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/gorilla/sessions&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Name of the cookie.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">sessionName</span> = <span style="color:#e6db74">&#34;fmessages&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">getCookieStore</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">sessions</span>.<span style="color:#a6e22e">CookieStore</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// In real-world applications, use env variables to store the session key.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">sessionKey</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;test-session-key&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">sessions</span>.<span style="color:#a6e22e">NewCookieStore</span>([]byte(<span style="color:#a6e22e">sessionKey</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Set adds a new message into the cookie storage.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">value</span> <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">session</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getCookieStore</span>().<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>(), <span style="color:#a6e22e">sessionName</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">AddFlash</span>(<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">name</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">Save</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>(), <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Response</span>())
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Get gets flash messages from the cookie storage.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>) []<span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">session</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getCookieStore</span>().<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>(), <span style="color:#a6e22e">sessionName</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fm</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">Flashes</span>(<span style="color:#a6e22e">name</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// If we have some messages.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">fm</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">Save</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Request</span>(), <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Response</span>())
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// Initiate a strings slice to return messages.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">flashes</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">fl</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">fm</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// Add message to the slice.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">flashes</span> = append(<span style="color:#a6e22e">flashes</span>, <span style="color:#a6e22e">fl</span>.(<span style="color:#66d9ef">string</span>))
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">flashes</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We now have a possibility to easily Set and Get flash messages. Let&rsquo;s integrate it, first, to the sign-in form, which we created in the <a href="https://webdevstation.com/posts/user-authentication-with-go-using-jwt-token/">previous article</a>.</p>
<p>I&rsquo;m going to modify <code>controllers/signin.go</code> file. Inside <code>SignIn()</code> function we can replace</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusUnauthorized</span>, <span style="color:#e6db74">&#34;Password is incorrect&#34;</span>)
</span></span></code></pre></div><p>with</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">c</span>, <span style="color:#e6db74">&#34;error&#34;</span>, <span style="color:#e6db74">&#34;Password is incorrect!&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Redirect</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusMovedPermanently</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Echo</span>().<span style="color:#a6e22e">Reverse</span>(<span style="color:#e6db74">&#34;userSignInForm&#34;</span>))
</span></span></code></pre></div><p>And, if the password is correct, we going to set a message also:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">c</span>, <span style="color:#e6db74">&#34;message&#34;</span>, <span style="color:#e6db74">&#34;Password is correct, you have been authenticated!&#34;</span>)
</span></span></code></pre></div><p>The final <code>SignIn()</code> function will look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// SignIn will be executed after SignInForm submission.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">SignIn</span>() <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// Load our &#34;test&#34; user.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">storedUser</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">LoadTestUser</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// Initiate a new User struct.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">u</span> <span style="color:#f92672">:=</span> new(<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// Parse the submitted data and fill the User struct with the data from the SignIn form.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Bind</span>(<span style="color:#a6e22e">u</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// Compare the stored hashed password, with the hashed version of the password that was received</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bcrypt</span>.<span style="color:#a6e22e">CompareHashAndPassword</span>([]byte(<span style="color:#a6e22e">storedUser</span>.<span style="color:#a6e22e">Password</span>), []byte(<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">Password</span>)); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// If the two passwords don&#39;t match, set a message and reload the page.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">c</span>, <span style="color:#e6db74">&#34;error&#34;</span>, <span style="color:#e6db74">&#34;Password is incorrect!&#34;</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Redirect</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusMovedPermanently</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Echo</span>().<span style="color:#a6e22e">Reverse</span>(<span style="color:#e6db74">&#34;userSignInForm&#34;</span>))
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// If password is correct, generate tokens and set cookies.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">auth</span>.<span style="color:#a6e22e">GenerateTokensAndSetCookies</span>(<span style="color:#a6e22e">storedUser</span>, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusUnauthorized</span>, <span style="color:#e6db74">&#34;Token is incorrect&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">c</span>, <span style="color:#e6db74">&#34;message&#34;</span>, <span style="color:#e6db74">&#34;Password is correct, you have been authenticated!&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Redirect</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusMovedPermanently</span>, <span style="color:#e6db74">&#34;/admin&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Once we set messages, we need to display them.</p>
<p>First, I will add message displaying logic to the SignIn form. We need to modify <code>SignInForm()</code> function inside <code>controllers/signin.go</code>, by adding additional data with messages to the template execution function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">template</span>.<span style="color:#a6e22e">ParseFiles</span>(<span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#e6db74">&#34;templates&#34;</span>, <span style="color:#e6db74">&#34;signIn.html&#34;</span>), <span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#e6db74">&#34;templates&#34;</span>, <span style="color:#e6db74">&#34;messages.html&#34;</span>))
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">data</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">interface</span>{})
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#34;errors&#34;</span>] = <span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">c</span>, <span style="color:#e6db74">&#34;error&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">tmpl</span>.<span style="color:#a6e22e">Execute</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Response</span>().<span style="color:#a6e22e">Writer</span>, <span style="color:#a6e22e">data</span>);
</span></span></code></pre></div><p>The final <code>SignInForm()</code> function will look like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// SignInForm responsible for signIn Form rendering.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">SignInForm</span>() <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">template</span>.<span style="color:#a6e22e">ParseFiles</span>(<span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#e6db74">&#34;templates&#34;</span>, <span style="color:#e6db74">&#34;signIn.html&#34;</span>), <span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#e6db74">&#34;templates&#34;</span>, <span style="color:#e6db74">&#34;messages.html&#34;</span>))
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">data</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">interface</span>{})
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">data</span>[<span style="color:#e6db74">&#34;errors&#34;</span>] = <span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">c</span>, <span style="color:#e6db74">&#34;error&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">tmpl</span>.<span style="color:#a6e22e">Execute</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Response</span>().<span style="color:#a6e22e">Writer</span>, <span style="color:#a6e22e">data</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>As you probably noticed, we introduced <code>messages.html</code> to the <code>template.ParseFiles()</code> function. There we will control how to visualize messages. Let&rsquo;s add this template inside <code>templates</code> folder:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>{{ define &#34;messages&#34; }}
</span></span><span style="display:flex;"><span>  {{ if index . &#34;errors&#34; }}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;errors&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;background: darksalmon; padding: 10px;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {{ range index . &#34;errors&#34; }}
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">p</span>&gt;{{ . }}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>        {{ end }}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  {{ end }}
</span></span><span style="display:flex;"><span>  {{ if index . &#34;messages&#34; }}
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;messages&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;background: darkseagreen; padding: 10px;&#34;</span>&gt;
</span></span><span style="display:flex;"><span>        {{ range index . &#34;messages&#34; }}
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">p</span>&gt;{{ . }}&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>        {{ end }}
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  {{ end }}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{{ end }}
</span></span></code></pre></div><p>Next, we need to add the reference to the <code>messages</code> template inside <code>signIn.html</code> and <code>admin.html</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>{{ <span style="color:#a6e22e">template</span> <span style="color:#e6db74">&#34;messages&#34;</span> . }}
</span></span></code></pre></div><p>Now, we can try how does it work.</p>
<p>Let&rsquo;s run the server <code>go run main.go</code> and go to the <code>/user/signin</code> path. When we submit the form with the wrong password, we should see this:
<img src="/images/0221/error.png" alt="Error Message" title="Error Message"></p>
<p>When a password is correct, we will be redirected to the <code>/admin</code> path with the message:
<img src="/images/0221/success.png" alt="Success Message" title="Success Message"></p>
<p>In this example, I was using the Echo framework, but it will be super easy to modify the code for your specific use-case.</p>
<p>The complete source code you can find <a href="https://github.com/alexsergivan/blog-examples/tree/master/flashmessages">here</a>.</p>
<p>Flash messages usually appear right after a login or a form post, so the natural companion pieces are <a href="/posts/user-authentication-with-go-using-jwt-token/">user authentication in Go Echo with JWT</a> and, for the layer that wraps every handler, <a href="/posts/go-middleware-example/">the Go middleware example</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Web Development</category>
    </item>
    <item>
      <title>User Authentication in Go Echo with JWT</title>
      <link>https://webdevstation.com/posts/user-authentication-with-go-using-jwt-token/</link>
      <pubDate>Thu, 28 Jan 2021 18:05:51 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/user-authentication-with-go-using-jwt-token/</guid>
      <description>Learn how to implement secure user authentication in Go using JWT (JSON Web Tokens) with the Echo framework. This step-by-step guide covers token creation,…</description>
      <content:encoded><![CDATA[<p>In this article, we will build a simple user authentication functionality using JWT (JSON Web Token).
In the examples, I&rsquo;m going to use a Go <a href="https://echo.labstack.com/">Echo</a> framework. This will allow us
to avoid writing some boilerplate code.</p>
<p>If you are not familiar with a JWT theory, please refer to <a href="https://jwt.io/introduction/">this resource</a>.</p>
<p>I believe that the easiest way to understand how to work with JWT authentication is by solving a real-world problem.
Let&rsquo;s say, that we have a website with an administration section, that should be accessible only by authenticated users, by providing some credentials. If authentication was successful, the user can access the administration section. If the user is inactive during a defined period of time, we should log him out from the system.</p>
<p>In the beginning, I&rsquo;m going to create a <code>main.go</code> file with initialization of the web server and some routers:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/alexsergivan/blog-examples/authentication/auth&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/alexsergivan/blog-examples/authentication/controllers&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/labstack/echo/v4/middleware&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Defining of the admin router group.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">adminGroup</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Group</span>(<span style="color:#e6db74">&#34;/admin&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Router for &#34;/admin&#34; path.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">adminGroup</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">controllers</span>.<span style="color:#a6e22e">Admin</span>())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Starting the server.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Logger</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Start</span>(<span style="color:#e6db74">&#34;:8777&#34;</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And controllers/admin.go:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">controllers</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Admin</span>() <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">String</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#e6db74">&#34;Hi, you have access!&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If you run this code and go to <code>http://localhost:8777/admin</code>, you will access this page without any authentication.
Let&rsquo;s protect this path, by adding a JWT authentication.</p>
<p>First, what I&rsquo;m going to create - it&rsquo;s an <code>auth</code> package, where we will keep all JWT related logic.
Please refer to the code below with added explanatory comments:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">auth</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/alexsergivan/blog-examples/authentication/user&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/dgrijalva/jwt-go&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">accessTokenCookieName</span>  = <span style="color:#e6db74">&#34;access-token&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Just for the demo purpose, I declared a secret here. In the real-world application, you might need to get it from the env variables.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">jwtSecretKey</span> = <span style="color:#e6db74">&#34;some-secret-key&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">GetJWTSecret</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">jwtSecretKey</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Create a struct that will be encoded to a JWT.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// We add jwt.StandardClaims as an embedded type, to provide fields like expiry time.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Claims</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Name</span>  <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;name&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">StandardClaims</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// GenerateTokensAndSetCookies generates jwt token and saves it to the http-only cookie.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">GenerateTokensAndSetCookies</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>, <span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">accessToken</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">generateAccessToken</span>(<span style="color:#a6e22e">user</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">setTokenCookie</span>(<span style="color:#a6e22e">accessTokenCookieName</span>, <span style="color:#a6e22e">accessToken</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">setUserCookie</span>(<span style="color:#a6e22e">user</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">generateAccessToken</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Declare the expiration time of the token (1h).</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">expirationTime</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Hour</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">generateToken</span>(<span style="color:#a6e22e">user</span>, <span style="color:#a6e22e">expirationTime</span>, []byte(<span style="color:#a6e22e">GetJWTSecret</span>()))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Pay attention to this function. It holds the main JWT token generation logic.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">generateToken</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>, <span style="color:#a6e22e">expirationTime</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#a6e22e">secret</span> []<span style="color:#66d9ef">byte</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Create the JWT claims, which includes the username and expiry time.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">claims</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Claims</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Name</span>:  <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">Name</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">StandardClaims</span>: <span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">StandardClaims</span>{
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// In JWT, the expiry time is expressed as unix milliseconds.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">ExpiresAt</span>: <span style="color:#a6e22e">expirationTime</span>.<span style="color:#a6e22e">Unix</span>(),
</span></span><span style="display:flex;"><span>		},
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Declare the token with the HS256 algorithm used for signing, and the claims.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">token</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">NewWithClaims</span>(<span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">SigningMethodHS256</span>, <span style="color:#a6e22e">claims</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Create the JWT string.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">tokenString</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">token</span>.<span style="color:#a6e22e">SignedString</span>(<span style="color:#a6e22e">secret</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>(), <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">tokenString</span>, <span style="color:#a6e22e">expirationTime</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Here we are creating a new cookie, which will store the valid JWT token.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">setTokenCookie</span>(<span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">token</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">expiration</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span> <span style="color:#f92672">:=</span> new(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Cookie</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Name</span> = <span style="color:#a6e22e">name</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Value</span> = <span style="color:#a6e22e">token</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Expires</span> = <span style="color:#a6e22e">expiration</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Path</span> = <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Http-only helps mitigate the risk of client side script accessing the protected cookie.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">HttpOnly</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetCookie</span>(<span style="color:#a6e22e">cookie</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Purpose of this cookie is to store the user&#39;s name.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">setUserCookie</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>, <span style="color:#a6e22e">expiration</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span> <span style="color:#f92672">:=</span> new(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Cookie</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Name</span> = <span style="color:#e6db74">&#34;user&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Value</span> = <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">Name</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Expires</span> = <span style="color:#a6e22e">expiration</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cookie</span>.<span style="color:#a6e22e">Path</span> = <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">SetCookie</span>(<span style="color:#a6e22e">cookie</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// JWTErrorChecker will be executed when user try to access a protected path.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">JWTErrorChecker</span>(<span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>, <span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Redirects to the signIn form.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Redirect</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusMovedPermanently</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Echo</span>().<span style="color:#a6e22e">Reverse</span>(<span style="color:#e6db74">&#34;userSignInForm&#34;</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>After finishing the main JWT token functionality, let&rsquo;s add the SignIn controllers, which will handle user authentication.
First, we need to add the new routers inside <code>main()</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>    <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/user/signin&#34;</span>, <span style="color:#a6e22e">controllers</span>.<span style="color:#a6e22e">SignInForm</span>()).<span style="color:#a6e22e">Name</span> = <span style="color:#e6db74">&#34;userSignInForm&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">POST</span>(<span style="color:#e6db74">&#34;/user/signin&#34;</span>, <span style="color:#a6e22e">controllers</span>.<span style="color:#a6e22e">SignIn</span>())
</span></span></code></pre></div><p>In the code below I created a <code>user</code> package with the user structure and a function that loads a dummy user from imaginary database. We gonna need it in our controllers to process and validate user data.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">user</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;golang.org/x/crypto/bcrypt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">User</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Password</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;password&#34; form:&#34;password&#34;`</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">Name</span> <span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;name&#34; form:&#34;name&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">LoadTestUser</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">User</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Just for demonstration purpose, we create a user with the encrypted &#34;test&#34; password.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// In real-world applications, you might load the user from the database by specific parameters (email, username, etc.)</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">hashedPassword</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bcrypt</span>.<span style="color:#a6e22e">GenerateFromPassword</span>([]byte(<span style="color:#e6db74">&#34;test&#34;</span>), <span style="color:#ae81ff">8</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">User</span>{<span style="color:#a6e22e">Password</span>: string(<span style="color:#a6e22e">hashedPassword</span>), <span style="color:#a6e22e">Name</span>: <span style="color:#e6db74">&#34;Test user&#34;</span>}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>After this, we will create a <code>controllers</code> package, where we add <code>SignInForm()</code> and <code>SignIn()</code> functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">controllers</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/alexsergivan/blog-examples/authentication/auth&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/alexsergivan/blog-examples/authentication/user&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;golang.org/x/crypto/bcrypt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;html/template&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;path&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// SignInForm responsible for signIn Form rendering.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">SignInForm</span>() <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#e6db74">&#34;templates&#34;</span>, <span style="color:#e6db74">&#34;signIn.html&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">template</span>.<span style="color:#a6e22e">ParseFiles</span>(<span style="color:#a6e22e">fp</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">tmpl</span>.<span style="color:#a6e22e">Execute</span>(<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Response</span>().<span style="color:#a6e22e">Writer</span>, <span style="color:#66d9ef">nil</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// SignIn will be executed after SignInForm submission.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">SignIn</span>() <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Load our &#34;test&#34; user.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">storedUser</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">LoadTestUser</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Initiate a new User struct.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">u</span> <span style="color:#f92672">:=</span> new(<span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Parse the submitted data and fill the User struct with the data from the SignIn form.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Bind</span>(<span style="color:#a6e22e">u</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Error</span>())
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// Compare the stored hashed password, with the hashed version of the password that was received.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bcrypt</span>.<span style="color:#a6e22e">CompareHashAndPassword</span>([]byte(<span style="color:#a6e22e">storedUser</span>.<span style="color:#a6e22e">Password</span>), []byte(<span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">Password</span>)); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// If the two passwords don&#39;t match, return a 401 status.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusUnauthorized</span>, <span style="color:#e6db74">&#34;Password is incorrect&#34;</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// If password is correct, generate tokens and set cookies.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">auth</span>.<span style="color:#a6e22e">GenerateTokensAndSetCookies</span>(<span style="color:#a6e22e">storedUser</span>, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">NewHTTPError</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusUnauthorized</span>, <span style="color:#e6db74">&#34;Token is incorrect&#34;</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Redirect</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusMovedPermanently</span>, <span style="color:#e6db74">&#34;/admin&#34;</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now, in the <code>/templates</code> folder we need to create a <code>signIn.html</code> template with the simple SignIn form:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span> <span style="color:#a6e22e">lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;en&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">class</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;w-full&#34;</span> <span style="color:#a6e22e">method</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;post&#34;</span> <span style="color:#a6e22e">action</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;/user/signin&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">label</span> <span style="color:#a6e22e">for</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;password&#34;</span>&gt;Password:&lt;/<span style="color:#f92672">label</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;password&#34;</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;password&#34;</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;password&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span>&gt;Sign In&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>Let&rsquo;s also modify the <code>admin</code> controller in <code>controllers/admin.go</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">controllers</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Admin</span>() <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Gets user cookie.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">userCookie</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Cookie</span>(<span style="color:#e6db74">&#34;user&#34;</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">String</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;Hi, %s! You have been authenticated!&#34;</span>, <span style="color:#a6e22e">userCookie</span>.<span style="color:#a6e22e">Value</span>))
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And finally, we need to add a JWT Middleware to the adminGroup path inside the <code>main()</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Read more about JWT Middleware here: https://echo.labstack.com/middleware/jwt</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">adminGroup</span>.<span style="color:#a6e22e">Use</span>(<span style="color:#a6e22e">middleware</span>.<span style="color:#a6e22e">JWTWithConfig</span>(<span style="color:#a6e22e">middleware</span>.<span style="color:#a6e22e">JWTConfig</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">Claims</span>:                  <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">auth</span>.<span style="color:#a6e22e">Claims</span>{},
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">SigningKey</span>:              []byte(<span style="color:#a6e22e">auth</span>.<span style="color:#a6e22e">GetJWTSecret</span>()),
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">TokenLookup</span>:             <span style="color:#e6db74">&#34;cookie:access-token&#34;</span>, <span style="color:#75715e">// &#34;&lt;source&gt;:&lt;name&gt;&#34;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ErrorHandlerWithContext</span>: <span style="color:#a6e22e">auth</span>.<span style="color:#a6e22e">JWTErrorChecker</span>,
</span></span><span style="display:flex;"><span>    }))
</span></span></code></pre></div><p>After that, execute <code>go run main.go</code>, to run the server, and go to <code>/admin</code> path. You will be redirected to the <code>/user/signin</code> path, because you need to be authenticated to access it. That is exactly what we need! Just enter <code>test</code> password and click on <code>Sign In</code> button. You will see this message: <code>Hi, Test user! You have been authenticated!</code> Awesome!</p>
<p>As you remember earlier, we set expiration time for the token: <code>expirationTime := time.Now().Add(1 * time.Hour)</code> It means, that after 1 hour user will be automatically logged-out. This is something what we want to prevent, especially if user is still active and doing some work on our resource.
This is possible to solve, by introducing a Refresh token. This token will have a much longer life-time and will be used for refreshing the Access token.
Let&rsquo;s modify our previous code.</p>
<p>First of all, we need to declare a secret for the Refresh token and cookie name to store the generated JWT. I will store it in a constant, but in the real-world applications please use environment variables for security reasons.</p>
<p>Inside <code>auth</code> package (/auth/auth.go) we need to add these modifications:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">accessTokenCookieName</span>  = <span style="color:#e6db74">&#34;access-token&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">refreshTokenCookieName</span> = <span style="color:#e6db74">&#34;refresh-token&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">jwtSecretKey</span> = <span style="color:#e6db74">&#34;some-secret-key&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">jwtRefreshSecretKey</span> = <span style="color:#e6db74">&#34;some-refresh-secret-key&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">GetRefreshJWTSecret</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">jwtRefreshSecretKey</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">GenerateTokensAndSetCookies</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>, <span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">accessToken</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">generateAccessToken</span>(<span style="color:#a6e22e">user</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">setTokenCookie</span>(<span style="color:#a6e22e">accessTokenCookieName</span>, <span style="color:#a6e22e">accessToken</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">setUserCookie</span>(<span style="color:#a6e22e">user</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// We generate here a new refresh token and saving it to the cookie.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">refreshToken</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">generateRefreshToken</span>(<span style="color:#a6e22e">user</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">setTokenCookie</span>(<span style="color:#a6e22e">refreshTokenCookieName</span>, <span style="color:#a6e22e">refreshToken</span>, <span style="color:#a6e22e">exp</span>, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">generateRefreshToken</span>(<span style="color:#a6e22e">user</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Time</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#75715e">// Declare the expiration time of the token - 24 hours.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">expirationTime</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">24</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Hour</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">generateToken</span>(<span style="color:#a6e22e">user</span>, <span style="color:#a6e22e">expirationTime</span>, []byte(<span style="color:#a6e22e">GetRefreshJWTSecret</span>()))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>At this point, when the user is signing-in, we generate 2 tokens: access and refresh. We still need to add logic for updating the access token, if the user is still active. For that, we can add a middleware, where we can check how much time is left for the user&rsquo;s access token, and if this time is less than some period of time (in this example it&rsquo;s 15 mins) we can generate the new tokens, by providing a valid refresh token.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// TokenRefresherMiddleware middleware, which refreshes JWT tokens if the access token is about to expire.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TokenRefresherMiddleware</span>(<span style="color:#a6e22e">next</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span>) <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">HandlerFunc</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// If the user is not authenticated (no user token data in the context), don&#39;t do anything.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;user&#34;</span>) <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">next</span>(<span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Gets user token from the context.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">u</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;user&#34;</span>).(<span style="color:#f92672">*</span><span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">Token</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">claims</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">u</span>.<span style="color:#a6e22e">Claims</span>.(<span style="color:#f92672">*</span><span style="color:#a6e22e">Claims</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// We ensure that a new token is not issued until enough time has elapsed.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// In this case, a new token will only be issued if the old token is within</span>
</span></span><span style="display:flex;"><span>		<span style="color:#75715e">// 15 mins of expiry.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Unix</span>(<span style="color:#a6e22e">claims</span>.<span style="color:#a6e22e">ExpiresAt</span>, <span style="color:#ae81ff">0</span>).<span style="color:#a6e22e">Sub</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>()) &lt; <span style="color:#ae81ff">15</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Minute</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Gets the refresh token from the cookie.</span>
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">rc</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Cookie</span>(<span style="color:#a6e22e">refreshTokenCookieName</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">rc</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// Parses token and checks if it valid.</span>
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">tkn</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">ParseWithClaims</span>(<span style="color:#a6e22e">rc</span>.<span style="color:#a6e22e">Value</span>, <span style="color:#a6e22e">claims</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">token</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">Token</span>) (<span style="color:#66d9ef">interface</span>{}, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>					<span style="color:#66d9ef">return</span> []byte(<span style="color:#a6e22e">GetRefreshJWTSecret</span>()), <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>				})
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>					<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">jwt</span>.<span style="color:#a6e22e">ErrSignatureInvalid</span> {
</span></span><span style="display:flex;"><span>						<span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Response</span>().<span style="color:#a6e22e">Writer</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusUnauthorized</span>)
</span></span><span style="display:flex;"><span>					}
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">tkn</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">tkn</span>.<span style="color:#a6e22e">Valid</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">// If everything is good, update tokens.</span>
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">GenerateTokensAndSetCookies</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">User</span>{
</span></span><span style="display:flex;"><span>						<span style="color:#a6e22e">Name</span>:  <span style="color:#a6e22e">claims</span>.<span style="color:#a6e22e">Name</span>,
</span></span><span style="display:flex;"><span>					}, <span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">next</span>(<span style="color:#a6e22e">c</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And finally, we have to attach our middleware to the <code>adminGroup</code> router inside the <code>main()</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>	<span style="color:#a6e22e">adminGroup</span>.<span style="color:#a6e22e">Use</span>(<span style="color:#a6e22e">auth</span>.<span style="color:#a6e22e">TokenRefresherMiddleware</span>)
</span></span></code></pre></div><p>You can run the server again and experiment, how does it work. As an example, you can change the access token lifetime to 1min and investigate how the jwt cookies behave.</p>
<p>That was pretty much it. I hope this article was helpful for you.</p>
<p>The complete source code you can found <a href="https://github.com/alexsergivan/blog-examples/tree/master/authentication">here</a>.</p>
<p>Authentication is only the first half. For deciding what an authenticated user is allowed to reach, see <a href="/posts/how-to-control-router-access-permissions-in-go-web-apps/">how to control router access permissions in Go web apps</a>; for keeping one client from consuming everyone&rsquo;s capacity, <a href="/posts/rate-limiting-go-apis/">rate limiting Go APIs</a>. And to tell the user what actually happened after a failed login, <a href="/posts/how-to-show-flash-messages-in-go-echo/">how to show flash messages in Go web applications</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Web Development</category>
      <category>Security</category>
    </item>
    <item>
      <title>How to Control Router Access Permissions in Go Web Apps</title>
      <link>https://webdevstation.com/posts/how-to-control-router-access-permissions-in-go-web-apps/</link>
      <pubDate>Wed, 23 Dec 2020 19:10:56 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/how-to-control-router-access-permissions-in-go-web-apps/</guid>
      <description>Learn how to implement URL-based access control and route permissions in Go web applications using the chi router middleware. A practical guide to securing your Go…</description>
      <content:encoded><![CDATA[<p>In this post I&rsquo;m going to describe how can we limit user access to the specific url in golang web application.
I will use <a href="https://github.com/go-chi/chi">chi</a> router - a lightweight, idiomatic and composable router for building
Go HTTP services.</p>
<p>Let&rsquo;s create our main package.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/go-chi/chi&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">chi</span>.<span style="color:#a6e22e">NewRouter</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">homePageHandler</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;/admin&#34;</span>, <span style="color:#a6e22e">adminPageHandler</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:3000&#34;</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">homePageHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#e6db74">&#34;This is home page&#34;</span>)) 
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">adminPageHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#e6db74">&#34;This is admin page&#34;</span>)) 
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>After this, if we go to the <code>/admin</code> page, we will see &ldquo;This is admin page&rdquo;.</p>
<p>Now, let&rsquo;s make this path accessible only for admin.</p>
<p>We have to replace</p>
<p><code>r.Get(&quot;/admin&quot;, adminPageHandler)</code>
With
<code>r.Mount(&quot;/admin&quot;, adminRouter())</code></p>
<p>Mount attaches another http.Handler or chi Router as a subrouter along a routing path.</p>
<p>Then, we have to attach middleware inside adminRouter() function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">adminRouter</span>() <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">chi</span>.<span style="color:#a6e22e">NewRouter</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Middleware with access rules for router.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Use</span>(<span style="color:#a6e22e">AdminOnly</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">adminPageHandler</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">r</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In this middleware we have a simple check is user authorized to access this page or not.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">AdminOnly</span>(<span style="color:#a6e22e">next</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// If user is admin, allows access.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">IsLoggedInAdmin</span>(<span style="color:#a6e22e">r</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">next</span>.<span style="color:#a6e22e">ServeHTTP</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Otherwise, 403.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusText</span>(<span style="color:#ae81ff">403</span>), <span style="color:#ae81ff">403</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In sake of demonstration, I&rsquo;m going just to use a random bool function to decide is used admin or not. You can modify this function according to your user authentication model.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">IsLoggedInAdmin</span>(<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Float32</span>() &lt; <span style="color:#ae81ff">0.5</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And that&rsquo;s it. Looks really simple, Isn&rsquo;t it?</p>
<p>Let&rsquo;s go to to the <code>/admin</code> page again.</p>
<p>As you see, now, sometimes (depends on our random decider), user has no access to this page anymore.</p>
<p>You can find source code <a href="https://github.com/alexsergivan/blog-examples/blob/master/route-auth/main.go">here</a></p>
<p>This builds directly on the pattern from <a href="/posts/go-middleware-example/">Go middleware example: how to alter a handler result</a>. For the authentication half of the problem, see <a href="/posts/user-authentication-with-go-using-jwt-token/">user authentication in Go Echo with JWT</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Web Development</category>
      <category>Security</category>
    </item>
    <item>
      <title>Go middleware example. How to alter a handler result</title>
      <link>https://webdevstation.com/posts/go-middleware-example/</link>
      <pubDate>Sun, 20 Dec 2020 20:12:51 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/go-middleware-example/</guid>
      <description>Learn how to implement Go middleware to intercept and modify HTTP handler responses with practical examples using chi router, demonstrating request/response…</description>
      <content:encoded><![CDATA[<p>Let&rsquo;s imagine a situation when you want to alter the result, returned by some http handler to the client.
Fortunately, Golang provides an easy mechanism for that, called a middleware.
I&rsquo;m going to dive directly to the source code, to save your time.</p>
<p>Imagine, we have this simple web server (here I&rsquo;m using a chi router):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;bytes&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;github.com/go-chi/chi&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">r</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">chi</span>.<span style="color:#a6e22e">NewRouter</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">myFirstHandler</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:3000&#34;</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">myFirstHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Write</span>([]byte(<span style="color:#e6db74">&#34;This is a main page&#34;</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>When we run this application and visit a frontpage htttp://localhost:3000/, we can see this:
<img src="/images/16122020/1.png" alt="Browser showing the JSON response returned by myFirstHandler on the front page" title="Response from the original Go handler"></p>
<p>Now we got a new requirement to create another handler which should get all response data
from myFirstHandler and add some modification on top.</p>
<p>We can do it easily in this way:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Adds a new router handler with a middleware myMiddleware.  </span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">With</span>(<span style="color:#a6e22e">myMiddleware</span>).<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;/other&#34;</span>, <span style="color:#a6e22e">myFirstHandler</span>) 
</span></span></code></pre></div><p>To be able to read a response from other handler, we have to implement our own <code>ResponseWriter</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MyResponseWriter</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">buf</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bytes</span>.<span style="color:#a6e22e">Buffer</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Here we are implementing a Write() function from ResponseWriter with our custom instructions. </span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">myrw</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">MyResponseWriter</span>) <span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">p</span> []<span style="color:#66d9ef">byte</span>) (<span style="color:#66d9ef">int</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">myrw</span>.<span style="color:#a6e22e">buf</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">p</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And finally, let&rsquo;s write our middleware:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">myMiddleware</span>(<span style="color:#a6e22e">next</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// Create a response writer:</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">myResponseWriter</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">MyResponseWriter</span>{
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">ResponseWriter</span>: <span style="color:#a6e22e">w</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">buf</span>:            <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">bytes</span>.<span style="color:#a6e22e">Buffer</span>{},
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// Here we are pssing our custom response writer to the next http handler.</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">next</span>.<span style="color:#a6e22e">ServeHTTP</span>(<span style="color:#a6e22e">myResponseWriter</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>      
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// Here we are adding our custom stuff to the response, which we received after http handler execution. </span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">myResponseWriter</span>.<span style="color:#a6e22e">buf</span>.<span style="color:#a6e22e">WriteString</span>(<span style="color:#e6db74">&#34; and some additional modifications&#34;</span>)
</span></span><span style="display:flex;"><span>      
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">// And, finally, we are copiing everything back to the original response writer.  if _, err := io.Copy(w, myResponseWriter.buf); err != nil {</span>
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">log</span>.<span style="color:#960050;background-color:#1e0010">`</span><span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;Failed to send out response: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>   })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Now, if we run our server again and go to <code>/other</code> path, we will see this:
<img src="/images/16122020/2.png" alt="Browser showing the same response after the middleware appended its extra field" title="Response after the Go middleware altered it"></p>
<p>This was a silly example, which will never happen in real life, but, I hope you got an overview how you can play
with http handlers and middlewares.</p>
<p>The source code you can found in this repository: <a href="https://github.com/alexsergivan/blog-examples/tree/master/middleware">https://github.com/alexsergivan/blog-examples/tree/master/middleware</a></p>
<p>Middleware is where a lot of cross-cutting concerns end up living. Three I have written about since: <a href="/posts/how-to-control-router-access-permissions-in-go-web-apps/">controlling router access permissions</a>, <a href="/posts/rate-limiting-go-apis/">rate limiting your API</a>, and <a href="/posts/structured-logging-in-go-with-slog/">putting a request-scoped logger in the context</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Web Development</category>
    </item>
  </channel>
</rss>
